From d2b772942e02ddc80447aa84cadd5a9a24048f5d Mon Sep 17 00:00:00 2001 From: Kyon <32325790+kyonRay@users.noreply.github.com> Date: Wed, 31 Jul 2024 14:29:00 +0800 Subject: [PATCH] (project): first commit for structure. (#1) --- .ci/ci_check.sh | 32 + .github/workflows/ci_check.yml | 26 + .gitignore | 4 + README.md | 8 +- build.gradle | 279 ++++++ docs/images/menu_logo_wecross.png | Bin 0 -> 7897 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 183 ++++ gradlew.bat | 100 ++ hooks/pre-commit | 33 + src/integTest/resources/WeCrossHub.sol | 189 ++++ src/integTest/resources/WeCrossProxy.sol | 924 ++++++++++++++++++ .../wecross/stub/web3/Web3StubFactory.java | 9 + 13 files changed, 1791 insertions(+), 1 deletion(-) create mode 100644 .ci/ci_check.sh create mode 100644 .github/workflows/ci_check.yml create mode 100644 build.gradle create mode 100644 docs/images/menu_logo_wecross.png create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100755 hooks/pre-commit create mode 100644 src/integTest/resources/WeCrossHub.sol create mode 100644 src/integTest/resources/WeCrossProxy.sol create mode 100644 src/main/java/com/webank/wecross/stub/web3/Web3StubFactory.java diff --git a/.ci/ci_check.sh b/.ci/ci_check.sh new file mode 100644 index 0000000..2afe6d7 --- /dev/null +++ b/.ci/ci_check.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -e +LOG_INFO() { + local content=${1} + echo -e "\033[32m ${content}\033[0m" +} + +get_sed_cmd() +{ + local sed_cmd="sed -i" + if [ "$(uname)" == "Darwin" ];then + sed_cmd="sed -i .bkp" + fi + echo "$sed_cmd" +} + +check_basic() +{ +# build +bash gradlew build assemble +} + +#LOG_INFO "------ download_build_chain---------" +#download_build_chain +LOG_INFO "------ check_basic---------" +check_basic +#LOG_INFO "------ check_ecdsa_evm_node---------" +#check_ecdsa_evm_node +#LOG_INFO "------ check_sm_evm_node---------" +#check_sm_evm_node + +#bash <(curl -s https://codecov.io/bash) diff --git a/.github/workflows/ci_check.yml b/.github/workflows/ci_check.yml new file mode 100644 index 0000000..3fc189d --- /dev/null +++ b/.github/workflows/ci_check.yml @@ -0,0 +1,26 @@ +# This workflow will build a Java project with Gradle +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle + +name: actions check + +on: [push, pull_request] + +jobs: + build: + name: build + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-22.04, ubuntu-20.04, macos-12] + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 5 + - name: install macOS dependencies + if: runner.os == 'macOS' + run: brew install openssl@1.1 openjdk@8 + - name: install Ubuntu dependencies + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt install -y git curl libssl-dev default-jdk build-essential + - name: run integration testing + run: /bin/bash .ci/ci_check.sh diff --git a/.gitignore b/.gitignore index 524f096..f14693f 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,7 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* replay_pid* + +.idea +.vscode +.gradle diff --git a/README.md b/README.md index 03acfe5..e835ec5 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ -# WeCross-Web3-Stub \ No newline at end of file +# WeCross-Web3-Stub + +[![CodeFactor](https://www.codefactor.io/repository/github/webankblockchain/WeCross-Web3-Stub/badge)](https://www.codefactor.io/repository/github/webankblockchain/WeCross-Web3-Stub) [![Build Status](https://travis-ci.org/WeBankBlockchain/WeCross-Web3-Stub.svg?branch=dev)](https://travis-ci.org/WeBankBlockchain/WeCross-Web3-Stub) [![Latest release](https://img.shields.io/github/release/WeBankBlockchain/WeCross-Web3-Stub.svg)](https://github.com/WeBankBlockchain/WeCross-Web3-Stub/releases/latest) +[![License](https://img.shields.io/github/license/WeBankBlockchain/WeCross-Web3-Stub)](https://www.apache.org/licenses/LICENSE-2.0) [![Language](https://img.shields.io/badge/Language-Java-blue.svg)](https://www.java.com) + +WeCross Web3 Stub是[WeCross](https://github.com/WeBankBlockchain/WeCross)用于适配[Web3J](https://github.com/hyperledger/web3j/)4.9.8及以上版本的插件。 + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..be140b2 --- /dev/null +++ b/build.gradle @@ -0,0 +1,279 @@ +plugins { + id 'java' + id 'idea' + id 'eclipse' + id 'maven' + id 'jacoco' + id 'maven-publish' + + id 'org.ajoberstar.grgit' version '4.1.1' + id 'com.diffplug.spotless' version '6.13.0' + id 'com.github.johnrengelman.shadow' version '6.1.0' +} +apply plugin: 'com.diffplug.spotless' +apply plugin: 'com.github.johnrengelman.shadow' + +group 'com.webank.wecross' +version '1.5.0' + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +// In this section you declare where to find the dependencies of your project +repositories { + // Use jcenter for resolving your dependencies. + // You can declare any Maven/Ivy/file repository here. + mavenCentral() + maven { url "https://maven.aliyun.com/nexus/content/groups/public/" } + maven { url "https://oss.sonatype.org/content/repositories/snapshots" } + maven { url "https://maven.aliyun.com/repository/gradle-plugin" } +} + +spotless { + java { + target 'src/*/java/**/*.java' + removeUnusedImports() + googleJavaFormat() + formatAnnotations() + } + + format 'misc', { + target '**/*.gradle', '**/*.md', '**/.gitignore' + } + format 'xml', { + target '**/*.xml' + } +} + +task copyHooks(type: Copy) { + println 'copyHooks task' + from("hooks") { + include "**" + } + into ".git/hooks" +} + +configurations { + all*.exclude group: 'org.java-websocket', module: 'Java-WebSocket' + all*.exclude group: 'org.antlr', module: '*' + all*.exclude group: 'de.vandermeer', module: '*' + all*.exclude group: 'com.alibaba', module: 'druid' + all*.exclude group: 'org.apache.httpcomponents', module: 'httpclient' + all*.exclude group: 'io.reactivex', module: 'rxjava' + all*.exclude group: 'org.ethereum', module: 'solcJ-all' + all*.exclude group: 'ch.qos.logback', module: 'logback-classic' + all*.exclude group: 'org.apache.logging.log4j', module: 'log4j-slf4j-impl' + all*.exclude group: 'com.alibaba', module: 'fastjson' + + integTestImplementation.extendsFrom implementation + integTestTestImplementation.extendsFrom testImplementation +} + +configurations.all { + resolutionStrategy.cacheChangingModulesFor 0, 'seconds' + exclude group: 'org.yaml', module: 'snakeyaml' +} + +def log4j_version = '2.22.1' +List logger = [ + "org.apache.logging.log4j:log4j-api:$log4j_version", + "org.apache.logging.log4j:log4j-core:$log4j_version", + "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version", + //"org.apache.logging.log4j:log4j-to-slf4j:$log4j_version", + "org.apache.logging.log4j:log4j-web:$log4j_version", + "org.apache.logging.log4j:log4j-jul:$log4j_version" +] + +dependencies { + implementation logger + implementation('com.webank:wecross-java-stub:1.4.0') { + exclude group: "io.netty" + exclude group: 'org.fisco-bcos', module: 'tcnative' + } + // implementation('org.fisco-bcos.java-sdk:fisco-bcos-sdk-abi:2.10.0-SNAPSHOT') + + // Use JUnit test framework + testImplementation 'junit:junit:4.13.2' +} + +sourceSets { + + main { + resources { + exclude '**/*.toml' + exclude '**/*.xml' + exclude '**/*.properties' + exclude '**/*.yml' + exclude '**/*.crt' + exclude '**/*.key' + } + } + + // Add the integration test directory + integTest { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + } + + resources { + srcDir file('src/integTest/resources') + } + } +} + +shadowJar { + dependsOn(copyHooks) + dependencies { + //排除掉io.netty:.*,不打包进fat jar + exclude(dependency('io.netty:.*')) + } + mergeServiceFiles() + minimize{ + //不优化处理io.grpc:.*,直接打进fat jar + exclude(dependency('io.grpc:.*')) + } +} + + +task makeJar(type: com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar, dependsOn: shadowJar) { + + destinationDir file('dist/apps') + archiveName project.name + "-" + project.version + '.jar' + exclude '**/*.xml' + exclude '**/*.toml' + exclude '**/*.properties' + exclude '**/*.yml' + exclude '**/performance/guomi/*' + + manifest { + try { + def repo = grgit.open(dir: file('.').canonicalPath) + if (repo != null) { + def date = new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") + def branch = repo.branch.getCurrent().getName() + def commit = repo.head().getAbbreviatedId(40) + + attributes(["Implementation-Timestamp": date, + "Git-Branch" : branch, + "Git-Commit" : commit + ]) + + logger.info(" Commit : ") + logger.info(" => date: {}", date) + logger.info(" => branch: {}", branch) + logger.info(" => commit: {}", commit) + } + } catch (Exception e) { + // logger.warn(' .git not exist, cannot found commit info') + } + } + + dependencies { + exclude(dependency('io.netty:.*')) + } + from zipTree(shadowJar.archivePath) +} + +jar.enabled = false +project.tasks.assemble.dependsOn(makeJar) + +task integTest(type: Test) { + description = 'Runs integration tests.' + + testClassesDirs = sourceSets.integTest.output.classesDirs + classpath = sourceSets.integTest.runtimeClasspath + mustRunAfter test +} + +// clean dist and log dir +task cleanDistAndLogDir(type: Delete) { + delete 'dist' + delete 'log' + delete 'out' +} + +tasks.clean.dependsOn(tasks.cleanDistAndLogDir) + +test { + testLogging { + showStandardStreams = true + events 'passed', 'skipped', 'failed' + } + +} + +integTest { + testLogging { + showStandardStreams = true + events 'passed', 'skipped', 'failed' + } + +} + +jacocoTestReport { + reports { + xml.enabled true + html.enabled true + //html.destination file("${buildDir}/jacocoHtml") + } +} + +ext { + if (!project.hasProperty("NEXUS_USERNAME")) { + NEXUS_USERNAME = "xxx" + } + + if (!project.hasProperty("NEXUS_PASSWORD")) { + NEXUS_PASSWORD = "xxx" + } +} + +publishing { + publications { + mavenJava(MavenPublication) { + groupId = 'com.webank' + artifactId = 'wecross-Web3-stub' + from components.java + artifacts = [shadowJar] + + // https://docs.gradle.org/current/dsl/org.gradle.api.publish.maven.MavenPublication.html + pom { + name = "WeCross-Web3-Stub" + description = "Stub interface for WeCross" + url = "https://github.com/WeBankBlockchain/WeCross" + licenses { + license { + name = "The Apache License, Version 2.0" + url = "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + developers { + developer { + id = "Shareong" + name = "Shareong" + email = "740310627@qq.com" + } + } + scm { + connection = "scm:git:git://github.com/WeBankBlockchain/WeCross.git" + developerConnection = "scm:git:ssh://github.com/WeBankBlockchain/WeCross.git" + url = "https://github.com/WeBankBlockchain/WeCross" + } + } + } + } + repositories { + maven { + def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2" + def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots" + url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl + + credentials { + username = NEXUS_USERNAME + password = NEXUS_PASSWORD + } + + } + } +} diff --git a/docs/images/menu_logo_wecross.png b/docs/images/menu_logo_wecross.png new file mode 100644 index 0000000000000000000000000000000000000000..58f16b756e0cd96e2a28d07aa72bfacc1eb5e4a5 GIT binary patch literal 7897 zcmch62{_dI+qZpRqGV?bl`zcMW??KDTSfLr$(R{T#tdT{OIc%-wM~dZMRt)T64{H8 zEZM0jOOmau&!|qP|MQ&loO8YJd%bgA^IPuw{@&~7zQ5mVCfdx@kefq(Y zLA&?Sz6aS@XxBOu!}{dN^0SQ2-fd8DMIK32?)} zu|RDtfF=b&Q{ayCMgu7B1fmy$f&_loi=aL4l9hmf?;_rANTBYnL4d7^893HQy?&;NmM|p>+CvGV2;L3pyC4?xOU}cGO!#gbi&4T6 zaPByww--$o@=MmkmE=wGawYwZ>R;^tFhEPKiODY;f9Z?6`!5q--uk|@Fn%WFFQvV# z{5^0=7C0}G4;h2g_ocZh{YNxj-WIq&^Z759)5L!*_QpH^&Dvege*}f2;QvN+m-9>1 z1EEL8p}k3DD-wzD2X@T<$Ra>bZx<_YfUF4`gD38$_V8{XKUd*UXm1=6s0>yHgCJm# zGTaIRLqMPiFiai{Lx90QL`_IoytDuBqRJqMsufL6jfTDd6s18Ki}ptUqc9eOa3+!6 z(X>L~-O(;MB@dzt5b&$K2t5*kM5Y<0d8hI_-B?f0j7)OI6KD)C3&SG-V|_h!2wYtq zq^t<}?yiXm!kFmgjV5An#wa9^7ONs2kEOW^gTvI&7!U*t2ZL1Av2YOD*@@O!$}lWi z9fLt*p|C&JqevK^U1;vE|Iwvb5{9PZPxn++)L;-8+8G4Hs=`1Rm?|0sM}yTtXb46P zs;cayh6BU@&~}21rws};;diaOUSVk((W=T2h#D3QQd7rbL8{JZH4q%GO1pr;Sanq_ z1`JoF6=4?}2$~w2OX%HhL;`7{eka=QVwB+Xg9udy(>_X&-Rn=j-9PaEWLp#Qv|1k$y|sprfUkX6rl63{SxkY*BbxX1u;Tp{h)a@lTfBZ!D<#?hyIjmk1-g7Y&~N zKgW_K&hzIJ0T1}ziU>63hyR#eAmgyWpJe=BRPo>C>xWuDR~#)<{|mN%hROPFSPGJ_z%UwRA9<*kP{Az0jWaJSQ-kQ(I7AmhEc;nVa^b&>L2m{ z?*Csl-2buvKNE~`MH5|cw4F-{__q;ZNJL*8`M0Wgpvh?3hJ_=0A%V_hk~;wH;X%M- zzT;BKmx%qj5BzEsfHw*7XNUi-9W0KF|1EdFTlwVz;Qyh$f2`EMn-~8RY5zYerL?;n z|7awo|K4Li-cdi&X|P+M?;954FT3eqqLH?PA!rluA7&yL1A)R-X_FY@1jm8YVXANt z4B`w1IjcBf(P~h2G!{!6B|p3&{ol^ST_Al=!*7R_KS;Eb&+g-|BNy%E*P#tZq~V=R zJCePQdo)Bxw=csOrE5jGHJ|2xYhQ0#-P7LQ9+i$^mAX2vmKd?{Tl_k}oIO&aVM5VH zHTtEgD>8h7SyZX?ad*wE`dF+$W@aG;6&BWVP?n7eSEUR9=c|tfQq8(3 zH&A3`OJS_jMQfyIW#&o0^-AD^-;@49btE@)Y3S7u=bmYi1fETH+pJsw`(~XcfRk;o zta{H^!%IUx#|}BNj57l-kwx~fq=YcmIXEU&ne}@ltUcy=C-`Y+?$oE6ngsAvg+9(;NtX|ilzMheuJ*<3$_{`0}4C=2bn%O*gg^uIh;&Yt}tFs2!{SO4>LLMD1TwvnK zVVcE8c~#tfDDAoI1*=_wJ(|6Nbym=b?5EVY9Tkmtc7hRuUsR%5jD{ zbKFA7*oKR}L^_12HBNl>J8XNw(L+vtS%l?m!1>NK;)Arsndenpg~KI_t_Irz zDOv1ODFtPIT5033EdG3Yo@t^f&T#U>j`*Y5NC%w?tCP8+EE_eo`r)Z=EHBS439Zz& zAJC0oTR5D%UAnK=D~zd&Fgy>hOQO2HiIb`e&aTu>BhWrhr>(m$%=_6lUnr?4WrI_+Tc63tDmK~sFBm2qQnex>%kJ-o>~Q$ z(*wgud>Zu#@vllv{!t0t8n-65H|cEe!5l&<@|GjS=a7(Pj|2q(@8>$IG&uYDZ0h}r zcx!rK?8S-kc@v2-@vDcBbvMFlul0$`OK0GK{uKXP*z(Yh(I~kM%FK&jegpFdIKrtu#&_M>!9jHejeyDoQbYI`d?%V_yGq zx&eoK;ocz5fu@pMT+2oKS}w#0eRJ--!P*rnp!1}o$~5dz#{@;wP6yH@U=G3v?-78p zx?Ba`#e1_e%hGAJ9iD(@6{0b^lDS*V<14eI6)owSYGQha@r%tF|TK}FD$ zv1uKvo61LLc@D^*mUy?luX|MMHDB+J_2s1i`uE-aM;f4tnS>3DK*lPQ;cO*+Tj(lySDnC#uvg$Y;C8$^3{|C z+fU|>(83R}-lCqkegElG}H_FJ== zTa-~2;vGiM^Z^BHHib{{$iOIv*1k+B??R+ewwVfxh>=-fF)?Cxb!zRS#zs#zZ|7J| z*{mtj{Wg#A{@x4R*F?>(^6^-35tgQxdZL%U0V`Y{KI;_~wwkNtqcF9iZh_v0up1qW zk<**zo-5kGwdz16_znsP)E%qas^g1#2vHpma}5rTV7n^H!|);`c|ap$9eDnB47NQ- z>>T9Op8E40&zt4+KvrF&@=2i^r^^_|UH0F;9dRm0M*^l&niw&Ry2vq3z&55Oe28{B zCd|dbobv@oUTMv{>DesiJH4Da_rY{Cg&VuDdLiA zm1RhqP1yTup5`Yu0P{}o&>kQQfB3YE1zNw+589F4=%mkX>)t<^oyBuQl@wnvQ-5rDVNd~^Q0RxkhcMQ zP?ge;d3N10hA1O*T5iLd(ZS$V{^NrdhvygCfT#{r^#q5ooO{`apDWPa7#CZzTI8E& z&J$Gba)>{D73<$T6YBl^;!wngW{5N0^nroTvV$(Run}rUnNNTw$0o4e*IL2HQuZ!> zUu5_@#`tR|Ca$pimB<$f-n*{R6$|Do=s1ve>El$C=Hq_JbMT_kBz>LNk{W}<12;WZ z@ca{UdC0oa{D_ES*SoJo!fx^33oQ?ylW$#0;26~)YE1 z`g|jU;z+MRd3vyx}p+01At(BPz@cEF+1;ryCSq}6=pLD0~H7Z8h- zO9Hq<1rn1j2^&}B$ap0k7wJ64;XS(IjnG3-&8YSp#M$yCg?@&p{N-tK)X}$RMqixq zZM+oBGWBYPotzpp#9qZFw;wp#HgdnSTH5Ww{lQyn^4X7F8Z)^-0&vQ`hZ^Uu7~|5va9VQ33O>XQ@^@ucs*VlimLG-=3MS zljn}KGgvh&b5m1Xo07V>YdDXW!L+!9Z3&k5pXs1_x>MXf3-JL{=5Ki+_Dg7!WH$A) z`k&4eYfG9~r02sTkC?oM`c%~$D#j~kOjr#ef zihsHE%v#St`y6#clQ+j;@`^7WZJ6-&V}QQ4lS1j&<7&wYO@nWon=%JPOC5chP8G#Y zpGVCUnO$R-_U)(4F>|8W9CVmeEzK2=-lG_~V z{3ig(b(T*$-+c`2Y2#8qC7|}UtX$DR+$lJhKcnNgwV3_8hdkE%QOElW3>d!Im=<p~kH5OwZ=K3fN1V@s}5a_LXq%<;zW{?ngHz8KqniwaT>(+TT2KiR?nR6HSp@ zFl0ejl(fa>xb)5Pi7uUQY?h242r9qnqHriF-*V4ag~8@Z9jn329xX#;P~Pd$(&f6T_fhOr-Zb{Xy%6NtL1qKThSZd$rK4` zMVFU!MQk^ws)UcN2!`EU!JoTdbE_)*w~_O`f`$y*}>t$>Z>7s}2y16C?T zpNb5^%UNSjnay)|x8R*RvkKPxcqUmt343c@J-8Fs$+2N+5gZX?rt>~ACVL;dN>1gK z#)}+ttj7}gM!9hVLjxLkZL={d?7{FIY11-+x+NZ*JfHnMW0H@cRNK4M61Gb%oHp>6 zrc4?RI^sM2>`}=&y2ye>+3G`wufNb)&Eq+zW7hNL?V?9V;nOb_6-8N_0}`uK%V5r= zd!vP-6Seds&`)XU7}to44ly&84HXRBC3d+o{8OdF>BqMxp0rD9F$T-(m}3rzMA~OC zcuwVrS;!}j`zz;I9k0l%u@0|Fb6J)^t=NZc@yn>eArP ze3sO-%=z6&uCrp=iz+hHN6FYhT6$RC^BJdh9Lgv{I$ z(jjXofD8;oi_aEWN99CE=a(3>OPOT($=1a!i``gBZ5(QnwmqwQ^VOi<*0YorizlzR z!e^FmB_n5_226JM@BJ3i~`2dD=xsK;Dne2F_hP5+K%rj1?Q_KgS8KfQ0( zpEL$ByGrF!n9mZxSuG(K%Ja?pJ!R zSn}Q@){{|c@wgl1#0dBmb1?G5V$GLS?#}L~6K!DYxSrwF+ut1NmI+9@=ZR*Oxg- z#Z`xn>DZ5GbhBO%h-Me4d~pf9S5kvwX#OdPf8oT@&mt@<0V1hSw2q#dp}c=m#ijA^ zy7YFEeC4KNuT$Ei@kTGBJDnhw z%$640m_c^3#+9q;(eL_tyj}I!i_mu$*4AInh~{X&uZ@?~5696XJJc1it|E4!%`J$F z_P+a=ME5V}B4I%Jfp=Hm9}?%!5qdw+DLX!@Q;`{3Ug)9X=Xm?*^~eDqUxy|-{<&CB z<(HR9m>^z%mN_SkPgx|ra$<7tIe`jcUJrP|;8lUK#$l((ZAX5Ub)~wCp(I_p#jTyx z7hgKcMkKXD=7v7!Pu}WDOm-JkrmOtsFzRZrt*faA;BE3U0(8~H>S&gjtMS5&p)th3 zQu>cyZ&DM21A8Vitn+qEyh+q6D++8%a!;1MfYh`amD+k^tv zm#CVHa(&$*7%`!OC68sFL*#j0X~rmCc55!3TNqRAfyEAhNNGu7{nm+|Vcy}nhxr1y z)-NRJoKEdEy8{%-)C0oeclb+0osOuo7@dO-y3TZ8jM`#WI)m6&R~NX^DO;dXCQG=z zeNAq1-aNs4Op%6tVKa?~X~S)mW9Kxf>-}y{`PuFarGh0%7T3l0EELov^etD%eSv#! zthanB8`Af<-36gdtJorMJ71N)fYqv;-UKY=a=D7-D^5nWJI`k~Fm{&KZLh8`3E4iH zJ;AC{JvvT~HZ%ke9nTeZ(-0ra@|2%BQ#$D>ZB=sM;Nr4WiMyng!d-W^J6U-b-{P-6 zpVs9%o&C-};lb%gvLL=;XQzQGH@8ImSljG1Hc^uuzYiz%y&FDQO{}=f^1O~eUGqi# zs0fetspLecz%9#e4V|DqD({L2v?7_$v>m7xK0HlL>Z%d<4Mvpjpkm$|6}o9gBj)CA zMjv+GwFKqu?!EhIt$4HoCRVm)$8R8oQaaQA6iTGDzwh5{8rVI#?#W!N}dm;hPWT{m(mbhs})t2KBy;T zcXQ3&kxCTm?`3R|^SeU(O-pAeE`r5C+CP-YA_Nu7ERd(joNF=3W#Yu%5Dv$NYK)W$ z-Ik8KK~L3tPE4*dcqeE%EVJBf87eczQ;(10^aH$VFTgpD^^^_}Z-{8vT{kb2P0t zJSgfY_G3NyxPKUn+1nS&MI)y~w>AX$my-GCy;x>Cj^@kW*<#5xp?2VGkXuyyuar%p zk*n0b1R#I@f`EG`b#+<@C`+2u;&eeT7l~$O+=^>&h8s;?K#-q^BrZG_?q(~`to7q= z9gV \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9618d8d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/hooks/pre-commit b/hooks/pre-commit new file mode 100755 index 0000000..807af40 --- /dev/null +++ b/hooks/pre-commit @@ -0,0 +1,33 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +bash gradlew spotlessCheck +result=$? +printf "the spotlessCheck result code is $result" +if [[ "$result" = 0 ]] ; then + echo "\033[32m + .... + .... + SpotlessCheck Pass!! + .... + .... + \033[0m" + exit 0 +else + bash gradlew spotlessApply + echo "\033[31m + .... + .... + SpotlessCheck Failed!! + Code format has been automatically adjusted, please review the code and then git add. && git commit; + .... + .... + \033[0m" + exit 1 +fi \ No newline at end of file diff --git a/src/integTest/resources/WeCrossHub.sol b/src/integTest/resources/WeCrossHub.sol new file mode 100644 index 0000000..45f203e --- /dev/null +++ b/src/integTest/resources/WeCrossHub.sol @@ -0,0 +1,189 @@ +pragma solidity >=0.5.0 <0.8.20; +pragma experimental ABIEncoderV2; + +contract WeCrossHub { + string constant private VERSION = "v1.0.0"; + + string constant private NULL_FLAG = "null"; + + string constant private CALL_TYPE_QUERY = "0"; + string constant private CALL_TYPE_INVOKE = "1"; + string constant private CALL_TYPE_GET_BLOCK = "2"; + + uint256 private increment = 0; + uint256 private currentIndex = 0; + + mapping(uint256 => string) private requests; + mapping(string => string[]) private callbackResults; + + function getVersion() public pure + returns (string memory) { + return VERSION; + } + + // get current uid + function getIncrement() public view + returns (uint256) { + return increment; + } + + // invoke other chain + function interchainInvoke(string memory _path, string memory _method, string[] memory _args, string memory _callbackPath, string memory _callbackMethod) public + returns (string memory uid) { + return handleRequest(CALL_TYPE_INVOKE, _path, _method, _args, _callbackPath, _callbackMethod); + } + + // query other chain, not support right now + function interchainQuery(string memory _path, string memory _method, string[] memory _args, string memory _callbackPath, string memory _callbackMethod) public + returns (string memory uid) { + return handleRequest(CALL_TYPE_QUERY, _path, _method, _args, _callbackPath, _callbackMethod); + } + + function interchainGetBlock(string memory _path, string memory _method, string[] memory _args, string memory _callbackPath, string memory _callbackMethod) public + returns (string memory uid) + { + return handleRequest(CALL_TYPE_GET_BLOCK, _path, _method, _args, _callbackPath, _callbackMethod); + } + + function handleRequest(string memory _callType, string memory _path, string memory _method, string[] memory _args, string memory _callbackPath, string memory _callbackMethod) private + returns (string memory uid) { + uid = uint256ToString(++increment); + + string[] memory reuqest = new string[](8); + reuqest[0] = uid; + reuqest[1] = _callType; + reuqest[2] = _path; + reuqest[3] = _method; + reuqest[4] = serializeStringArray(_args); + reuqest[5] = _callbackPath; + reuqest[6] = _callbackMethod; + reuqest[7] = addressToString(tx.origin); + + requests[increment] = serializeStringArray(reuqest); + } + + function getInterchainRequests(uint256 _num) public view + returns (string memory) { + if (currentIndex == increment) { + return NULL_FLAG; + } + + uint256 num = _num < (increment - currentIndex) + ? _num + : (increment - currentIndex); + + string[] memory tempRequests = new string[](num); + for (uint256 i = 0; i < num; i++) { + tempRequests[i] = requests[currentIndex + i + 1]; + } + + return serializeStringArray(tempRequests); + } + + function updateCurrentRequestIndex(uint256 _index) public { + if (currentIndex < _index) { + currentIndex = _index; + } + } + + // _result is json form of arrays + function registerCallbackResult(string memory _uid, string memory _tid, string memory _seq, string memory _errorCode, string memory _errorMsg, string[] memory _result) public { + string[5] memory result = [_tid, _seq, _errorCode, _errorMsg, serializeStringArray(_result)]; + callbackResults[_uid] = result; + } + + function selectCallbackResult(string memory _uid) public view + returns (string[] memory) { + return callbackResults[_uid]; + } + + function serializeStringArray(string[] memory _arr) internal pure + returns (string memory jsonStr) { + uint256 len = _arr.length; + if (len == 0) { + return "[]"; + } + + jsonStr = "["; + for (uint256 i = 0; i < len - 1; i++) { + jsonStr = string(abi.encodePacked(jsonStr, '"')); + jsonStr = string(abi.encodePacked(jsonStr, jsonEscape(_arr[i]))); + jsonStr = string(abi.encodePacked(jsonStr, '",')); + } + + jsonStr = string(abi.encodePacked(jsonStr, '"')); + jsonStr = string(abi.encodePacked(jsonStr, jsonEscape(_arr[len - 1]))); + jsonStr = string(abi.encodePacked(jsonStr, '"')); + jsonStr = string(abi.encodePacked(jsonStr, "]")); + } + + function jsonEscape(string memory _str) internal pure + returns (string memory) { + bytes memory bts = bytes(_str); + uint256 len = bts.length; + bytes memory temp = new bytes(len * 2); + uint256 i = 0; + uint256 j = 0; + for (; j < len; j++) { + if (bts[j] == "\\" || bts[j] == '"') { + temp[i++] = "\\"; + } + temp[i++] = bts[j]; + } + + bytes memory res = new bytes(i); + for (j = 0; j < i; j++) { + res[j] = temp[j]; + } + return string(res); + } + + function uint256ToString(uint256 _value) internal pure + returns (string memory) { + bytes32 result; + if (_value == 0) { + return "0"; + } + + while (_value > 0) { + result = bytes32(uint256(result) / (2 ** 8)); + result |= bytes32(((_value % 10) + 48) * 2 ** (8 * 31)); + _value /= 10; + } + return bytes32ToString(result); + } + + function bytes32ToString(bytes32 _bts32) internal pure + returns (string memory) { + bytes memory result = new bytes(_bts32.length); + + uint256 len = _bts32.length; + for (uint256 i = 0; i < len; i++) { + result[i] = _bts32[i]; + } + + return string(result); + } + + function addressToString(address _addr) internal pure + returns (string memory) { + bytes memory result = new bytes(40); + for (uint256 i = 0; i < 20; i++) { + bytes1 temp = bytes1(uint8(uint160(_addr) / (2 ** (8 * (19 - i))))); + bytes1 b1 = bytes1(uint8(temp) / 16); + bytes1 b2 = bytes1(uint8(temp) - 16 * uint8(b1)); + result[2 * i] = convert(b1); + result[2 * i + 1] = convert(b2); + } + return string(abi.encodePacked("0x", string(result))); + } + + function convert(bytes1 _b) internal pure + returns (bytes1) { + if (uint8(_b) < 10) { + return bytes1(uint8(_b) + 0x30); + } else { + return bytes1(uint8(_b) + 0x57); + } + } +} \ No newline at end of file diff --git a/src/integTest/resources/WeCrossProxy.sol b/src/integTest/resources/WeCrossProxy.sol new file mode 100644 index 0000000..94233e7 --- /dev/null +++ b/src/integTest/resources/WeCrossProxy.sol @@ -0,0 +1,924 @@ +pragma solidity >=0.5.0 <0.8.20; +pragma experimental ABIEncoderV2; + +contract WeCrossProxy { + string constant private version = "v1.0.0"; + + struct XATransactionStep { + string accountIdentity; + uint256 timestamp; + string path; + address contractAddress; + string func; + bytes args; + } + + struct XATransaction { + string accountIdentity; + string[] paths; // all paths related to this transaction + address[] contractAddresses; // locked addressed in current chain + string status; // processing | committed | rolledback + uint256 startTimestamp; + uint256 commitTimestamp; + uint256 rollbackTimestamp; + uint256[] seqs; // sequence of each step + uint256 stepNum; // step number + } + + struct ContractStatus { + bool locked; // isolation control, read-committed + string xaTransactionID; + } + + mapping(string => address) private cnsContracts; // key: contractName + mapping(address => ContractStatus) private lockedContracts; // key: contractAddress + mapping(string => XATransaction) private xaTransactions; // key: xaTransactionID + mapping(string => XATransactionStep) private xaTransactionSteps; // key: xaTransactionID || xaTransactionSeq + mapping(string => Transaction) private transactions; // key: uniqueID + /* + * record all xa transactionIDs + * head: point to the current xa transaction to be checked + * tail: point to the next position for added xa transaction + */ + uint256 private head = 0; + uint256 private tail = 0; + string[] private xaTransactionIDs; + + string constant private XA_STATUS_PROCESSING = "processing"; + string constant private XA_STATUS_COMMITTED = "committed"; + string constant private XA_STATUS_ROLLEDBACK = "rolledback"; + + string constant private REVERT_FLAG = "_revert"; + string constant private NULL_FLAG = "null"; + string constant private SUCCESS_FLAG = "success"; + + bytes1 constant private SEPARATOR = "."; + + uint256 constant private MAX_STEP = 1024; + + string[] private resourceCache; + + struct Transaction { + bool existed; + bytes result; + } + + function getVersion() public pure + returns (string memory) { + return version; + } + + function addResource(string memory _name) public { + resourceCache.push(_name); + } + + function getResources() public view + returns (string[] memory) { + return resourceCache; + } + + function deleteResources() public { + delete resourceCache; + } + + function deployContract(bytes memory _bin) internal + returns (address _addr) { + bool ok = false; + assembly { + _addr := create(0, add(_bin, 0x20), mload(_bin)) + ok := gt(extcodesize(_addr), 0) + } + if (!ok) { + revert("deploy contract failed"); + } + } + + function deployContractWithRegisterCNS(string memory _path, bytes memory _bin) public + returns (address) { + string memory name = getNameByPath(_path); + address addr = getAddressByName(name, false); + if ((addr != address(0x0)) && lockedContracts[addr].locked) { + revert(string(abi.encodePacked(name, " is locked by unfinished xa transaction: ", lockedContracts[addr].xaTransactionID))); + } + + address deploy_addr = deployContract(_bin); + + cnsContracts[name] = deploy_addr; + + if (addr == address(0x0)) { + addResource(name); + } + return deploy_addr; + } + + function registerCNS(string memory _path, address _addr) public { + string memory name = getNameByPath(_path); + address addr = getAddressByName(name, false); + if ((addr != address(0x0)) && lockedContracts[addr].locked) { + revert(string(abi.encodePacked(name, " is locked by unfinished xa transaction: ", lockedContracts[addr].xaTransactionID))); + } + + cnsContracts[name] = _addr; + + if (addr == address(0x0)) { + addResource(name); + } + } + + function selectByName(string memory _name) public view + returns (address) { + return cnsContracts[_name]; + } + + function constantCall(string memory _name, bytes memory _argsWithMethodId) public + returns (bytes memory){ + address addr = getAddressByName(_name, true); + + if (lockedContracts[addr].locked) { + revert( + string(abi.encodePacked("resource is locked by unfinished xa transaction: ", lockedContracts[addr].xaTransactionID)) + ); + } + + return callContract(addr, _argsWithMethodId); + } + + function constantCallWithXa(string memory _XATransactionID, string memory _path, string memory _func, bytes memory _args) public + returns (bytes memory) { + address addr = getAddressByPath(_path, true); + + if (!isExistedXATransaction(_XATransactionID)) { + revert("xa transaction not found"); + } + + if (!sameString(lockedContracts[addr].xaTransactionID, _XATransactionID)) { + revert( + string(abi.encodePacked(_path, " is unregistered in xa transaction: ", _XATransactionID)) + ); + } + + return callContract(addr, _func, _args); + } + + function sendTransaction(string memory _uid, string memory _name, bytes memory _argsWithMethodId) public + returns (bytes memory) { + if (transactions[_uid].existed) { + return transactions[_uid].result; + } + + address addr = getAddressByName(_name, true); + + if (lockedContracts[addr].locked) { + revert( + string(abi.encodePacked(_name, " is locked by unfinished xa transaction: ", lockedContracts[addr].xaTransactionID)) + ); + } + + bytes memory result = callContract(addr, _argsWithMethodId); + + transactions[_uid] = Transaction(true, result); + return result; + } + + function sendTransactionWithXa(string memory _uid, string memory _XATransactionID, uint256 _XATransactionSeq, string memory _path, string memory _func, bytes memory _args) public + returns (bytes memory) { + if (transactions[_uid].existed) { + return transactions[_uid].result; + } + + address addr = getAddressByPath(_path, true); + + if (!isExistedXATransaction(_XATransactionID)) { + revert("xa transaction not found"); + } + + if (sameString(xaTransactions[_XATransactionID].status, XA_STATUS_COMMITTED)) { + revert("xa transaction has been committed"); + } + + if (sameString(xaTransactions[_XATransactionID].status, XA_STATUS_ROLLEDBACK)) { + revert("xa transaction has been rolledback"); + } + + if (!sameString(lockedContracts[addr].xaTransactionID, _XATransactionID)) { + revert( + string(abi.encodePacked(_path, " is unregistered in xa transaction ", _XATransactionID)) + ); + } + + if (!isValidXATransactionSep(_XATransactionID, _XATransactionSeq)) { + revert("seq should be greater than before"); + } + + // recode step + xaTransactionSteps[getXATransactionStepKey(_XATransactionID, _XATransactionSeq)] = XATransactionStep( + addressToString(tx.origin), + block.timestamp / 1000, + _path, + addr, + _func, + _args + ); + + // recode seq + uint256 num = xaTransactions[_XATransactionID].stepNum; + xaTransactions[_XATransactionID].seqs[num] = _XATransactionSeq; + xaTransactions[_XATransactionID].stepNum = num + 1; + + bytes memory result = callContract(addr, _func, _args); + + // recode transaction + transactions[_uid] = Transaction(true, result); + return result; + } + + function startXATransaction(string memory _xaTransactionID, string[] memory _selfPaths, string[] memory _otherPaths) public + returns (string memory) { + if (isExistedXATransaction(_xaTransactionID)) { + revert(string(abi.encodePacked("xa transaction ", _xaTransactionID, " already exists"))); + } + + uint256 selfLen = _selfPaths.length; + uint256 otherLen = _otherPaths.length; + + address[] memory contracts = new address[](selfLen); + string[] memory allPaths = new string[](selfLen + otherLen); + + for (uint256 i = 0; i < selfLen; i++) { + address addr = getAddressByPath(_selfPaths[i], true); + contracts[i] = addr; + if (lockedContracts[addr].locked) { + revert(string(abi.encodePacked(_selfPaths[i], " is locked by unfinished xa transaction: ", lockedContracts[addr].xaTransactionID))); + } + lockedContracts[addr].locked = true; + lockedContracts[addr].xaTransactionID = _xaTransactionID; + allPaths[i] = _selfPaths[i]; + } + + for (uint256 i = 0; i < otherLen; i++) { + allPaths[selfLen + i] = _otherPaths[i]; + } + + uint256[] memory seqs = new uint256[](MAX_STEP); + + xaTransactions[_xaTransactionID] = XATransaction( + addressToString(tx.origin), + allPaths, + contracts, + XA_STATUS_PROCESSING, + block.timestamp / 1000, + 0, + 0, + seqs, + 0 + ); + + addXATransaction(_xaTransactionID); + + return SUCCESS_FLAG; + } + + function commitXATransaction(string memory _xaTransactionID) public + returns (string memory) + { + if (!isExistedXATransaction(_xaTransactionID)) { + revert("xa transaction not found"); + } + + if (sameString(xaTransactions[_xaTransactionID].status, XA_STATUS_COMMITTED)) { + revert("xa transaction has been committed"); + } + + // has rolledback + if (sameString(xaTransactions[_xaTransactionID].status, XA_STATUS_ROLLEDBACK)) { + revert("xa transaction has been rolledback"); + } + + xaTransactions[_xaTransactionID].commitTimestamp = block.timestamp / 1000; + xaTransactions[_xaTransactionID].status = XA_STATUS_COMMITTED; + deleteLockedContracts(_xaTransactionID); + head++; + + return SUCCESS_FLAG; + } + + + function rollbackXATransaction(string memory _xaTransactionID) public + returns (string memory) { + string memory result = SUCCESS_FLAG; + if (!isExistedXATransaction(_xaTransactionID)) { + revert("xa transaction not found"); + } + + if (sameString(xaTransactions[_xaTransactionID].status, XA_STATUS_COMMITTED)) { + revert("xa transaction has been committed"); + } + + if (sameString(xaTransactions[_xaTransactionID].status, XA_STATUS_ROLLEDBACK)) { + revert("xa transaction has been rolledback"); + } + + string memory message = "warning:"; + uint256 stepNum = xaTransactions[_xaTransactionID].stepNum; + for (uint256 i = stepNum; i > 0; i--) { + uint256 seq = xaTransactions[_xaTransactionID].seqs[i - 1]; + string memory key = getXATransactionStepKey(_xaTransactionID, seq); + + string memory func = xaTransactionSteps[key].func; + address contractAddress = xaTransactionSteps[key].contractAddress; + bytes memory args = xaTransactionSteps[key].args; + + bytes memory sig = abi.encodeWithSignature( + getRevertFunc(func, REVERT_FLAG) + ); + bool success; + (success,) = address(contractAddress).call( + abi.encodePacked(sig, args) + ); + if (!success) { + message = string( + abi.encodePacked(message, ' revert "', func, '" failed.') + ); + result = message; + } + } + + xaTransactions[_xaTransactionID].rollbackTimestamp = block.timestamp / 1000; + xaTransactions[_xaTransactionID].status = XA_STATUS_ROLLEDBACK; + deleteLockedContracts(_xaTransactionID); + return result; + } + + function getXATransactionNumber() public view + returns (string memory) { + if (xaTransactionIDs.length == 0) { + return "0"; + } else { + return uint256ToString(xaTransactionIDs.length); + } + } + + /* + * traverse in reverse order + * outputs: + { + "total": 100, + "xaTransactions": + [ + { + "xaTransactionID": "001", + "accountIdentity": "0x11", + "status": "processing", + "timestamp": 123, + "paths": ["a.b.1","a.b.2"] + }, + { + "xaTransactionID": "002", + "accountIdentity": "0x11", + "status": "committed", + "timestamp": 123, + "paths": ["a.b.1","a.b.2"] + } + ] + } + */ + function listXATransactions(string memory _index, uint256 _size) public view + returns (string memory) { + uint256 len = xaTransactionIDs.length; + uint256 index = sameString("-1", _index) ? (len - 1) : stringToUint256(_index); + + if (len == 0 || len <= index) { + return '{"total":0,"xaTransactions":[]}'; + } + + string memory jsonStr = "["; + for (uint256 i = 0; i < (_size - 1) && (index - i) > 0; i++) { + string memory xaTransactionID = xaTransactionIDs[index - i]; + jsonStr = string( + abi.encodePacked( + jsonStr, + '{"xaTransactionID":"', + xaTransactionID, + '",', + '"accountIdentity":"', + xaTransactions[xaTransactionID].accountIdentity, + '",', + '"status":"', + xaTransactions[xaTransactionID].status, + '",', + '"paths":', + pathsToJson(xaTransactionID), + ",", + '"timestamp":', + uint256ToString( + xaTransactions[xaTransactionID].startTimestamp + ), + "}," + ) + ); + } + + uint256 lastIndex = (index + 1) >= _size ? (index + 1 - _size) : 0; + string memory xaTransactionID = xaTransactionIDs[lastIndex]; + jsonStr = string( + abi.encodePacked( + jsonStr, + '{"xaTransactionID":"', + xaTransactionID, + '",', + '"accountIdentity":"', + xaTransactions[xaTransactionID].accountIdentity, + '",', + '"status":"', + xaTransactions[xaTransactionID].status, + '",', + '"paths":', + pathsToJson(xaTransactionID), + ",", + '"timestamp":', + uint256ToString(xaTransactions[xaTransactionID].startTimestamp), + "}]" + ) + ); + + return + string( + abi.encodePacked( + '{"total":', + uint256ToString(len), + ',"xaTransactions":', + jsonStr, + "}" + ) + ); + } + + /* + * @param xaTransactionID + * result with json form + * example: + { + "xaTransactionID": "1", + "accountIdentity": "0x88", + "status": "processing", + "paths":["a.b.c1","a.b.c2","a.b1.c3"], + "startTimestamp": 123, + "commitTimestamp": 456, + "rollbackTimestamp": 0, + "xaTransactionSteps": [{ + "accountIdentity":"0x12", + "xaTransactionSeq": 233, + "path": "a.b.c1", + "timestamp": 233, + "method": "set", + "args": "0010101" + }, + { + "accountIdentity":"0x12", + "xaTransactionSeq": 244, + "path": "a.b.c2", + "timestamp": 244, + "method": "set", + "args": "0010101" + } + ] + } + */ + function getXATransaction(string memory _xaTransactionID) public view + returns (string memory) { + if (!isExistedXATransaction(_xaTransactionID)) { + revert("xa transaction not found"); + } + + return + string( + abi.encodePacked( + '{"xaTransactionID":"', + _xaTransactionID, + '",', + '"accountIdentity":"', + xaTransactions[_xaTransactionID].accountIdentity, + '",', + '"status":"', + xaTransactions[_xaTransactionID].status, + '",', + '"paths":', + pathsToJson(_xaTransactionID), + ",", + '"startTimestamp":', + uint256ToString( + xaTransactions[_xaTransactionID].startTimestamp + ), + ",", + '"commitTimestamp":', + uint256ToString( + xaTransactions[_xaTransactionID].commitTimestamp + ), + ",", + '"rollbackTimestamp":', + uint256ToString( + xaTransactions[_xaTransactionID].rollbackTimestamp + ), + ",", + '"xaTransactionSteps":', + xaTransactionStepArrayToJson( + _xaTransactionID, + xaTransactions[_xaTransactionID].seqs, + xaTransactions[_xaTransactionID].stepNum + ), + "}" + ) + ); + } + + // called by router to check xa transaction status + function getLatestXATransaction() public view + returns (string memory) { + string memory xaTransactionID; + if (head == tail) { + return "{}"; + } else { + xaTransactionID = xaTransactionIDs[uint256(head)]; + } + return getXATransaction(xaTransactionID); + } + + // called by router to rollback transaction + function rollbackAndDeleteXATransactionTask(string memory _xaTransactionID) public + returns (string memory) { + rollbackXATransaction(_xaTransactionID); + return deleteXATransactionTask(_xaTransactionID); + } + + function getLatestXATransactionID() public view + returns (string memory) { + if (head == tail) { + return NULL_FLAG; + } else { + return xaTransactionIDs[uint256(head)]; + } + } + + function getXATransactionState(string memory _path) public view + returns (string memory) { + address addr = getAddressByPath(_path, true); + if (!lockedContracts[addr].locked) { + return NULL_FLAG; + } else { + string memory xaTransactionID = lockedContracts[addr] + .xaTransactionID; + uint256 index = xaTransactions[xaTransactionID].stepNum; + uint256 seq = index == 0 ? 0 : xaTransactions[xaTransactionID].seqs[index - 1]; + return string(abi.encodePacked(xaTransactionID, " ", uint256ToString(seq))); + } + } + + function addXATransaction(string memory _xaTransactionID) internal { + tail++; + xaTransactionIDs.push(_xaTransactionID); + } + + function deleteXATransactionTask(string memory _xaTransactionID) internal + returns (string memory) { + if (head == tail) { + revert("delete nonexistent xa transaction"); + } + + if (!sameString(xaTransactionIDs[head], _xaTransactionID)) { + revert("delete unmatched xa transaction"); + } + + head++; + return SUCCESS_FLAG; + } + + function callContract(address _contractAddress, string memory _sig, bytes memory _args) internal + returns (bytes memory result) { + bytes memory sig = abi.encodeWithSignature(_sig); + bool success; + (success, result) = address(_contractAddress).call( + abi.encodePacked(sig, _args) + ); + if (!success) { + revert(string(result)); + } + } + + function callContract(address _contractAddress, bytes memory _argsWithMethodId) internal + returns (bytes memory result) { + bool success; + (success, result) = address(_contractAddress).call(_argsWithMethodId); + if (!success) { + //(string memory error) = abi.decode(result, (string)); + revert(string(result)); + } + } + + function getAddressByName(string memory _name, bool _revertNotExist) internal view + returns (address _address) { + _address = cnsContracts[_name]; + if (_address == address(0x0)) { + if (_revertNotExist) { + revert("the name's address not exist."); + } + } + } + + function getAddressByPath(string memory _path, bool _revertNotExist) internal view + returns (address) { + string memory name = getNameByPath(_path); + return getAddressByName(name, _revertNotExist); + } + + // input must be a valid path like "zone.chain.resource" + function getNameByPath(string memory _path) internal pure + returns (string memory) { + bytes memory path = bytes(_path); + uint256 len = path.length; + uint256 nameLen = 0; + uint256 index = 0; + for (uint256 i = len - 1; i > 0; i--) { + if (path[i] == SEPARATOR) { + index = i + 1; + break; + } else { + nameLen++; + } + } + + bytes memory name = new bytes(nameLen); + for (uint256 i = 0; i < nameLen; i++) { + name[i] = path[index++]; + } + + return string(name); + } + + /* + ["a.b.c1", "a.b.c2"] + */ + function pathsToJson(string memory _transactionID) internal view + returns (string memory) { + uint256 len = xaTransactions[_transactionID].paths.length; + string memory paths = string( + abi.encodePacked('["', xaTransactions[_transactionID].paths[0], '"') + ); + for (uint256 i = 1; i < len; i++) { + paths = string( + abi.encodePacked( + paths, + ',"', + xaTransactions[_transactionID].paths[i], + '"' + ) + ); + } + return string(abi.encodePacked(paths, "]")); + } + + /* + [ + { + "accountIdentity":"0x12", + "xaTransactionSeq": 233, + "path": "a.b.c1", + "timestamp": 233, + "method": "set", + "args": "0010101" + }, + { + "accountIdentity":"0x12", + "xaTransactionSeq": 233, + "path": "a.b.c1", + "timestamp": 233, + "method": "set", + "args": "0010101" + } + ] + */ + function xaTransactionStepArrayToJson(string memory _transactionID, uint256[] memory _seqs, uint256 _len) internal view + returns (string memory result) { + if (_len == 0) { + return "[]"; + } + + result = string( + abi.encodePacked("[", xaTransactionStepToJson(xaTransactionSteps[getXATransactionStepKey(_transactionID, _seqs[0])], _seqs[0])) + ); + for (uint256 i = 1; i < _len; i++) { + result = string( + abi.encodePacked(result, ",", xaTransactionStepToJson(xaTransactionSteps[getXATransactionStepKey(_transactionID, _seqs[i])], _seqs[i])) + ); + } + + return string(abi.encodePacked(result, "]")); + } + + /* + { + "xaTransactionSeq": 233, + "accountIdentity":"0x12", + "path": "a.b.c1", + "timestamp": 233, + "method": "set", + "args": "0010101" + } + */ + function xaTransactionStepToJson(XATransactionStep memory _xaTransactionStep, uint256 _XATransactionSeq) internal pure + returns (string memory) { + return + string( + abi.encodePacked( + '{"xaTransactionSeq":', + uint256ToString(_XATransactionSeq), + ",", + '"accountIdentity":"', + _xaTransactionStep.accountIdentity, + '",', + '"path":"', + _xaTransactionStep.path, + '",', + '"timestamp":', + uint256ToString(_xaTransactionStep.timestamp), + ",", + '"method":"', + getMethodFromFunc(_xaTransactionStep.func), + '",', + '"args":"', + bytesToHexString(_xaTransactionStep.args), + '"}' + ) + ); + } + + function isExistedXATransaction(string memory _xaTransactionID) internal view + returns (bool) { + return xaTransactions[_xaTransactionID].startTimestamp != 0; + } + + function isValidXATransactionSep(string memory _xaTransactionID, uint256 _XATransactionSeq) internal view + returns (bool) { + uint256 index = xaTransactions[_xaTransactionID].stepNum; + return (index == 0) || (_XATransactionSeq > xaTransactions[_xaTransactionID].seqs[index - 1]); + } + + function deleteLockedContracts(string memory _xaTransactionID) internal { + uint256 len = xaTransactions[_xaTransactionID].contractAddresses.length; + for (uint256 i = 0; i < len; i++) { + address contractAddress = xaTransactions[_xaTransactionID].contractAddresses[i]; + delete lockedContracts[contractAddress]; + } + } + + // func(string,uint256) => func_flag(string,uint256) + function getRevertFunc(string memory _func, string memory _revertFlag) internal pure + returns (string memory) { + bytes memory funcBytes = bytes(_func); + bytes memory flagBytes = bytes(_revertFlag); + uint256 funcLen = funcBytes.length; + uint256 flagLen = flagBytes.length; + bytes memory newFunc = new bytes(funcLen + flagLen); + + bytes1 c = bytes1("("); + uint256 index = 0; + uint256 point = 0; + + for (uint256 i = 0; i < funcLen; i++) { + if (funcBytes[i] != c) { + newFunc[index++] = funcBytes[i]; + } else { + point = i; + break; + } + } + + for (uint256 i = 0; i < flagLen; i++) { + newFunc[index++] = flagBytes[i]; + } + + for (uint256 i = point; i < funcLen; i++) { + newFunc[index++] = funcBytes[i]; + } + + return string(newFunc); + } + + // func(string,uint256) => func + function getMethodFromFunc(string memory _func) internal pure + returns (string memory) { + bytes memory funcBytes = bytes(_func); + uint256 funcLen = funcBytes.length; + bytes memory temp = new bytes(funcLen); + + bytes1 c = bytes1("("); + uint256 index = 0; + + for (uint256 i = 0; i < funcLen; i++) { + if (funcBytes[i] != c) { + temp[index++] = funcBytes[i]; + } else { + break; + } + } + + bytes memory result = new bytes(index); + for (uint256 i = 0; i < index; i++) { + result[i] = temp[i]; + } + + return string(result); + } + + function getXATransactionStepKey(string memory _transactionID, uint256 _transactionSeq) internal pure + returns (string memory) { + return + string( + abi.encodePacked( + _transactionID, + uint256ToString(_transactionSeq) + ) + ); + } + + function sameString(string memory _str1, string memory _str2) internal pure + returns (bool) { + return keccak256(bytes(_str1)) == keccak256(bytes(_str2)); + } + + function stringToUint256(string memory _str) internal pure + returns (uint256) { + bytes memory bts = bytes(_str); + uint256 result = 0; + uint256 len = bts.length; + for (uint256 i = 0; i < len; i++) { + if (uint8(bts[i]) >= 48 && uint8(bts[i]) <= 57) { + result = result * 10 + (uint8(bts[i]) - 48); + } + } + return result; + } + + function uint256ToString(uint256 _value) internal pure + returns (string memory) { + bytes32 result; + if (_value == 0) { + return "0"; + } else { + while (_value > 0) { + result = bytes32(uint256(result) / (2 ** 8)); + result |= bytes32(((_value % 10) + 48) * 2 ** (8 * 31)); + _value /= 10; + } + } + return bytes32ToString(result); + } + + function bytesToHexString(bytes memory _bts) internal pure + returns (string memory result) { + uint256 len = _bts.length; + bytes memory s = new bytes(len * 2); + for (uint256 i = 0; i < len; i++) { + bytes1 befor = bytes1(_bts[i]); + bytes1 high = bytes1(uint8(befor) / 16); + bytes1 low = bytes1(uint8(befor) - 16 * uint8(high)); + s[i * 2] = convert(high); + s[i * 2 + 1] = convert(low); + } + result = string(s); + } + + function bytes32ToString(bytes32 _bts32) internal pure + returns (string memory) { + bytes memory result = new bytes(_bts32.length); + + uint256 len = _bts32.length; + for (uint256 i = 0; i < len; i++) { + result[i] = _bts32[i]; + } + + return string(result); + } + + function addressToString(address _addr) internal pure + returns (string memory) { + bytes memory result = new bytes(40); + for (uint256 i = 0; i < 20; i++) { + bytes1 temp = bytes1(uint8(uint160(_addr) / (2 ** (8 * (19 - i))))); + bytes1 b1 = bytes1(uint8(temp) / 16); + bytes1 b2 = bytes1(uint8(temp) - 16 * uint8(b1)); + result[2 * i] = convert(b1); + result[2 * i + 1] = convert(b2); + } + + return string(abi.encodePacked("0x", string(result))); + } + + function convert(bytes1 _b) internal pure + returns (bytes1) { + if (uint8(_b) < 10) { + return bytes1(uint8(_b) + 0x30); + } else { + return bytes1(uint8(_b) + 0x57); + } + } +} diff --git a/src/main/java/com/webank/wecross/stub/web3/Web3StubFactory.java b/src/main/java/com/webank/wecross/stub/web3/Web3StubFactory.java new file mode 100644 index 0000000..4e54558 --- /dev/null +++ b/src/main/java/com/webank/wecross/stub/web3/Web3StubFactory.java @@ -0,0 +1,9 @@ +package com.webank.wecross.stub.web3; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Web3StubFactory { + private final Logger logger = LoggerFactory.getLogger(Web3StubFactory.class); + +}