diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5921977c3..f8bdbc017 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -109,12 +109,14 @@ jobs: interop-fabtoken-t4, interop-fabtoken-t5, interop-fabtoken-t6, + interop-fabtoken-t7, interop-dlog-t1, interop-dlog-t2, interop-dlog-t3, interop-dlog-t4, interop-dlog-t5, interop-dlog-t6, + interop-dlog-t7, dlogstress-t1, ] diff --git a/Makefile b/Makefile index 1c20afdc8..b8301015c 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ FABRIC_VERSION ?= 2.5.0 FABRIC_CA_VERSION ?= 1.5.7 FABRIC_TWO_DIGIT_VERSION = $(shell echo $(FABRIC_VERSION) | cut -d '.' -f 1,2) ORION_VERSION=v0.2.10 +WEAVER_VERSION=1.2.1 # need to install fabric binaries outside of fts tree for now (due to chaincode packaging issues) FABRIC_BINARY_BASE=$(PWD)/../fabric @@ -48,7 +49,7 @@ install-softhsm: ./ci/scripts/install_softhsm.sh .PHONY: docker-images -docker-images: fabric-docker-images orion-server-images monitoring-docker-images testing-docker-images +docker-images: fabric-docker-images orion-server-images monitoring-docker-images testing-docker-images weaver-docker-images .PHONY: testing-docker-images testing-docker-images: @@ -76,6 +77,12 @@ orion-server-images: docker pull orionbcdb/orion-server:$(ORION_VERSION) docker image tag orionbcdb/orion-server:$(ORION_VERSION) orionbcdb/orion-server:latest +.PHONY: weaver-docker-images +weaver-docker-images: + docker pull ghcr.io/hyperledger-labs/weaver-fabric-driver:$(WEAVER_VERSION) + docker image tag ghcr.io/hyperledger-labs/weaver-fabric-driver:$(WEAVER_VERSION) hyperledger-labs/weaver-fabric-driver:latest + docker pull ghcr.io/hyperledger-labs/weaver-relay-server:$(WEAVER_VERSION) + docker image tag ghcr.io/hyperledger-labs/weaver-relay-server:$(WEAVER_VERSION) hyperledger-labs/weaver-relay-server:latest .PHONY: integration-tests-nft-dlog integration-tests-nft-dlog: diff --git a/go.mod b/go.mod index 801e82ebf..827946202 100644 --- a/go.mod +++ b/go.mod @@ -134,6 +134,8 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huin/goupnp v1.3.0 // indirect + github.com/hyperledger-labs/weaver-dlt-interoperability/common/protos-go v1.2.3-alpha.1 // indirect + github.com/hyperledger-labs/weaver-dlt-interoperability/sdks/fabric/go-sdk v1.2.3-alpha.1.0.20210812140206-37f430515b8c // indirect github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 // indirect github.com/hyperledger/fabric-private-chaincode v1.0.0-rc3.0.20231026135044-67a19b0fcda0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index 1cf529935..2fb88a148 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c/go.mod h1:hSVuE3qU7grINVSwrmzHfpg9k87ALBk+XaualNyUzI4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -626,6 +627,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= +github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/IBM/idemix v0.0.2-0.20240816143710-3dce4618d760 h1:KkXCAlR4QIFcDc+/PNVCzGbSGs9sunN852ob8tUwA64= github.com/IBM/idemix v0.0.2-0.20240816143710-3dce4618d760/go.mod h1:VJKQspYKuHLuG8ukNHfFsFgD+NaJHlNO58ZWShlnPi4= github.com/IBM/idemix/bccsp/schemes/aries v0.0.0-20240612072411-114d281b442d h1:8w1yWLIk0VzLTHFE1O8GwCaXs5hT9M7u4KsO+IwIen0= @@ -637,6 +640,7 @@ github.com/IBM/idemix/bccsp/types v0.0.0-20240816143710-3dce4618d760/go.mod h1:4 github.com/IBM/mathlib v0.0.3-0.20231011094432-44ee0eb539da h1:qqGozq4tF6EOVnWoTgBoJGudRKKZXSAYnEtDggzTnsw= github.com/IBM/mathlib v0.0.3-0.20231011094432-44ee0eb539da/go.mod h1:Tco9QzE3fQzjMS7nPbHDeFfydAzctStf1Pa8hsh6Hjs= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= +github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -654,10 +658,13 @@ github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/ale-linux/aries-framework-go/component/kmscrypto v0.0.0-20231023164747-f3f972769504 h1:sQyFeDcHVHWJ3IeE437NSJjv0+J/6MvGQOJew4X+Cuw= github.com/ale-linux/aries-framework-go/component/kmscrypto v0.0.0-20231023164747-f3f972769504/go.mod h1:z5xq4Ji1RQojJLZzKeZH5+LKCVZxgQRZpQ4xAJWi8r0= github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 h1:t3eaIm0rUkzbrIewtiFmMK5RXHej2XnoXNhxVsAYUfg= github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= @@ -672,6 +679,7 @@ github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= @@ -695,6 +703,7 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -706,6 +715,10 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY= +github.com/cloudflare/cfssl v1.4.1/go.mod h1:KManx/OJPb5QY+y0+o/898AMcM128sF0bURvoVUSjTo= +github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4= +github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= @@ -756,6 +769,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -826,6 +840,7 @@ github.com/fsouza/go-dockerclient v1.12.0 h1:S2f2crEUbBNCFiF06kR/GvioEB8EMsb3Td/ github.com/fsouza/go-dockerclient v1.12.0/go.mod h1:YWUtjg8japrqD/80L98nTtCoxQFp5B5wrSsnyeB5lFo= github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= +github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw= @@ -844,6 +859,7 @@ github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmn github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -851,6 +867,8 @@ github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= @@ -873,6 +891,7 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= @@ -948,6 +967,7 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= @@ -1070,28 +1090,35 @@ github.com/hidal-go/hidalgo v0.0.0-20201109092204-05749a6d73df/go.mod h1:bPkrxDl github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= -github.com/hyperledger-labs/fabric-smart-client v0.3.1-0.20241111074600-334cc6d09cd6 h1:muVqNcNGwDkPs6L5d/KZOSy5Y/YIwWRHCi4Ox3AbSC4= -github.com/hyperledger-labs/fabric-smart-client v0.3.1-0.20241111074600-334cc6d09cd6/go.mod h1:vZLAiVznnEdHyx1OV5nIqf34apnfdhonwJL+qEYniIQ= github.com/hyperledger-labs/fabric-smart-client v0.3.1-0.20241114072958-8bdbea854812 h1:1GGnfSHFRKfnCZtho6Ia8ZneaGsI5abKDqXsdL8hVSo= github.com/hyperledger-labs/fabric-smart-client v0.3.1-0.20241114072958-8bdbea854812/go.mod h1:vZLAiVznnEdHyx1OV5nIqf34apnfdhonwJL+qEYniIQ= github.com/hyperledger-labs/orion-sdk-go v0.2.10 h1:lFgWgxyvngIhWnIqymYGBmtmq9D6uC5d0uLG9cbyh5s= github.com/hyperledger-labs/orion-sdk-go v0.2.10/go.mod h1:iN2xZB964AqwVJwL+EnwPOs8z1EkMEbbIg/qYeC7gDY= github.com/hyperledger-labs/orion-server v0.2.10 h1:G4zbQEL5Egk0Oj+TwHCZWdTOLDBHOjaAEvYOT4G7ozw= github.com/hyperledger-labs/orion-server v0.2.10/go.mod h1:PfuEZFOxbR1o1TjdqL7gQXWD3B0WFET58u9p40rGN+Q= +github.com/hyperledger-labs/weaver-dlt-interoperability/common/protos-go v1.2.3-alpha.1 h1:vBvo0PNm82ht7wpBjlYY4ZHxV3YprCfdVd3T4JG9vBw= +github.com/hyperledger-labs/weaver-dlt-interoperability/common/protos-go v1.2.3-alpha.1/go.mod h1:POCGO/RK9YDfgdhuyqjoD9tRNtWfK7Rh5AYYmsb1Chc= +github.com/hyperledger-labs/weaver-dlt-interoperability/sdks/fabric/go-sdk v1.2.3-alpha.1.0.20210812140206-37f430515b8c h1:pKr8VnHlduEgdInwLWykYAw+lpUizjQJaJ8I5fVoRUo= +github.com/hyperledger-labs/weaver-dlt-interoperability/sdks/fabric/go-sdk v1.2.3-alpha.1.0.20210812140206-37f430515b8c/go.mod h1:si2XAWZclHXC359OyYMpNHfonf2P7P2nzABdCA8mPqs= github.com/hyperledger/fabric v1.4.0-rc1.0.20230405174026-695dd57e01c2 h1:w5BGxCYEsc9vjdDEdZGrZ5redvs263RYsdT2tqF7cNk= github.com/hyperledger/fabric v1.4.0-rc1.0.20230405174026-695dd57e01c2/go.mod h1:LSwfuRgX/5C2uHkdT3hJtBFu/ALxuL7dFj1pmBby2R4= github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 h1:B1Nt8hKb//KvgGRprk0h1t4lCnwhE9/ryb1WqfZbV+M= github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2/go.mod h1:X+DIyUsaTmalOpmpQfIvFZjKHQedrURQ5t4YqquX7lE= github.com/hyperledger/fabric-chaincode-go v0.0.0-20240704073638-9fb89180dc17 h1:SCsBjYLaoHCuyN6D3AAEX+YjBEnXn7MVpxn3rNX5gu4= github.com/hyperledger/fabric-chaincode-go v0.0.0-20240704073638-9fb89180dc17/go.mod h1:6R5/nmBVrNVvk76xqH30j/ecqphXD3zS6gCeYPKK4nk= +github.com/hyperledger/fabric-config v0.0.5/go.mod h1:YpITBI/+ZayA3XWY5lF302K7PAsFYjEEPM/zr3hegA8= github.com/hyperledger/fabric-config v0.1.0 h1:TsR3y5xEoUmXWfp8tcDycjJhVvXEHiV5kfZIxuIte08= github.com/hyperledger/fabric-config v0.1.0/go.mod h1:aeDZ0moG/qKvwLjddcqYr8+58/oNaJy3HE0tI01546c= +github.com/hyperledger/fabric-lib-go v1.0.0/go.mod h1:H362nMlunurmHwkYqR5uHL2UDWbQdbfz74n8kbCFsqc= github.com/hyperledger/fabric-lib-go v1.1.2 h1:3eHwudGZC5Ex7go5UAzVKhpF34gypPZGfSZksBKLWvE= github.com/hyperledger/fabric-lib-go v1.1.2/go.mod h1:SHNCq8AB0VpHAmvJEtdbzabv6NNV1F48JdmDihasBjc= github.com/hyperledger/fabric-private-chaincode v1.0.0-rc3.0.20231026135044-67a19b0fcda0 h1:TLVkWq6aTVttBJ5WiHMeh/gufo+lPnYN9rVuEb8qXRs= github.com/hyperledger/fabric-private-chaincode v1.0.0-rc3.0.20231026135044-67a19b0fcda0/go.mod h1:Q7jUjJfkL/5ZmO6c0DL0DKjN63Yp7ULe9tun+9G76eY= +github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/hyperledger/fabric-protos-go v0.0.0-20200707132912-fee30f3ccd23/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= github.com/hyperledger/fabric-protos-go v0.3.3 h1:0nssqz8QWJNVNBVQz+IIfAd2j1ku7QPKFSM/1anKizI= github.com/hyperledger/fabric-protos-go v0.3.3/go.mod h1:BPXse9gIOQwyAePQrwQVUcc44bTW4bB5V3tujuvyArk= +github.com/hyperledger/fabric-sdk-go v1.0.0/go.mod h1:qWE9Syfg1KbwNjtILk70bJLilnmCvllIYFCSY/pa1RU= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -1133,18 +1160,23 @@ github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZl github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jellydator/ttlcache/v2 v2.11.1 h1:AZGME43Eh2Vv3giG6GeqeLeFXxwxn1/qHItqWZl6U64= github.com/jellydator/ttlcache/v2 v2.11.1/go.mod h1:RtE5Snf0/57e+2cLWFYWCCsLas2Hy3c5Z4n14XmSvTI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo= +github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= @@ -1153,6 +1185,8 @@ github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhd github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= +github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= @@ -1167,6 +1201,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -1177,12 +1212,14 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v0.0.0-20180201184707-88edab080323/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= @@ -1222,6 +1259,7 @@ github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuz github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -1232,12 +1270,14 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= @@ -1256,6 +1296,7 @@ github.com/miracl/conflate v1.3.4 h1:BI+pOex3Pp7DtSaTDqArYDMKSSDYUvRjb66lgAHhSFw github.com/miracl/conflate v1.3.4/go.mod h1:7S2L/ymkFyEgv8oM5G6sKxTExeRkLqxPBJgNz7g1ys0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= @@ -1276,6 +1317,7 @@ github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= @@ -1284,6 +1326,7 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= @@ -1310,12 +1353,14 @@ github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/n github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -1341,6 +1386,7 @@ github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLyw github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= @@ -1356,6 +1402,7 @@ github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3 github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -1372,6 +1419,7 @@ github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= @@ -1421,6 +1469,7 @@ github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUY github.com/pion/webrtc/v3 v3.3.0 h1:Rf4u6n6U5t5sUxhYPQk/samzU/oDv7jk6BA5hyO2F9I= github.com/pion/webrtc/v3 v3.3.0/go.mod h1:hVmrDJvwhEertRWObeb1xzulzHGeVUoPlWvxdGzcfU0= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1434,18 +1483,27 @@ github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= @@ -1507,8 +1565,11 @@ github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go. github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= @@ -1523,21 +1584,25 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.3.1/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.1.1/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= @@ -1591,10 +1656,14 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= +github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= +github.com/weppos/publicsuffix-go v0.5.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/wlynxg/anet v0.0.3 h1:PvR53psxFXstc12jelG6f1Lv4MWqE0tI76/hHGjh9rg= @@ -1622,6 +1691,11 @@ github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFi github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= +github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= +github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e/go.mod h1:w7kd3qXHh8FNaczNjslXqvFQiv5mMWRXlL9klTUAHc8= +github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb/go.mod h1:29UiAJNsiVdvTBFCJW8e3q6dcDbOoPkhMgttOSCIMMY= go.etcd.io/etcd v0.5.0-alpha.5.0.20210226220824-aa7126864d82 h1:RCaUKN0yRYKT2JzV9kH4u+D6l9VWcJMQ449QKRriFc8= go.etcd.io/etcd v0.5.0-alpha.5.0.20210226220824-aa7126864d82/go.mod h1:WWRiAtnzDdtuCMxtFwME/Knea11a6fJgJkwtC1QSc/k= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= @@ -1691,6 +1765,7 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -1699,8 +1774,10 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -1783,6 +1860,7 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1791,6 +1869,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1902,8 +1981,10 @@ golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1916,6 +1997,7 @@ golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2432,6 +2514,7 @@ google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/integration/nwo/token/topology.go b/integration/nwo/token/topology.go index 0ff19d4e5..0aa2715e5 100755 --- a/integration/nwo/token/topology.go +++ b/integration/nwo/token/topology.go @@ -7,9 +7,7 @@ SPDX-License-Identifier: Apache-2.0 package token import ( - "github.com/hyperledger-labs/fabric-smart-client/integration/nwo/fsc" "github.com/hyperledger-labs/fabric-smart-client/integration/nwo/fsc/node" - "github.com/hyperledger-labs/fabric-smart-client/pkg/api" "github.com/hyperledger-labs/fabric-token-sdk/integration/nwo/token/topology" "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" . "github.com/onsi/gomega" @@ -84,12 +82,6 @@ func (t *Topology) AddTMS(fscNodes []*node.Node, backend BackedTopology, channel return tms } -func (t *Topology) SetSDK(fscTopology *fsc.Topology, sdk api.SDK) { - for _, node := range fscTopology.Nodes { - node.AddSDK(sdk) - } -} - func (t *Topology) GetTMSs() []*topology.TMS { return t.TMSs } diff --git a/integration/ports.go b/integration/ports.go index 148a3a333..c2d570a89 100755 --- a/integration/ports.go +++ b/integration/ports.go @@ -97,6 +97,8 @@ const ( ZKATDLogInteropHTLCSwapNoCrossTwoFabricNetworks ZKATDLogInteropHTLCOrion ZKATDLogInteropHTLCSwapNoCrossWithOrionAndFabricNetworks + FabTokenInteropAssetTransfer + ZKATDLogInteropAssetTransfer Mixed ) diff --git a/integration/token/common/sdk/fall/sdk.go b/integration/token/common/sdk/fall/sdk.go index 58ab96a25..c1471eb42 100644 --- a/integration/token/common/sdk/fall/sdk.go +++ b/integration/token/common/sdk/fall/sdk.go @@ -7,13 +7,22 @@ SPDX-License-Identifier: Apache-2.0 package fall import ( + "context" "errors" "github.com/hyperledger-labs/fabric-smart-client/pkg/node" dig2 "github.com/hyperledger-labs/fabric-smart-client/platform/common/sdk/dig" + digutils "github.com/hyperledger-labs/fabric-smart-client/platform/common/utils/dig" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/services/weaver" fabtoken "github.com/hyperledger-labs/fabric-token-sdk/token/core/fabtoken/driver" + fabric3 "github.com/hyperledger-labs/fabric-token-sdk/token/core/fabtoken/driver/interop/state/fabric" dlog "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/nogh/driver" + fabric4 "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/nogh/driver/interop/state/fabric" tokensdk "github.com/hyperledger-labs/fabric-token-sdk/token/sdk/dig" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state/driver" + fabric2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state/fabric" "github.com/hyperledger-labs/fabric-token-sdk/token/services/network/fabric" "go.uber.org/dig" ) @@ -40,5 +49,58 @@ func (p *SDK) Install() error { return err } - return p.SDK.Install() + fabricEnabled := p.ConfigService().GetBool("fabric.enabled") + if fabricEnabled { + err := errors.Join( + // weaver + p.Container().Provide(weaver.NewProvider, dig.As(new(fabric2.RelayProvider))), + // state provider + p.Container().Provide(state.NewServiceProvider), + p.Container().Provide(fabric3.NewStateDriver, dig.Group("fabric-ssp-state-drivers")), + p.Container().Provide(fabric4.NewStateDriver, dig.Group("fabric-ssp-state-drivers")), + p.Container().Provide(fabric2.NewSSPDriver, dig.Group("ssp-drivers")), + p.Container().Provide(pledge.NewVaultStore), + ) + if err != nil { + return err + } + } + + if err := p.SDK.Install(); err != nil { + return err + } + + if fabricEnabled { + return errors.Join( + digutils.Register[fabric2.RelayProvider](p.Container()), + digutils.Register[*pledge.VaultStore](p.Container()), + digutils.Register[*state.ServiceProvider](p.Container()), + ) + } + + return nil +} + +func (p *SDK) Start(ctx context.Context) error { + if err := p.SDK.Start(ctx); err != nil { + return err + } + + fabricEnabled := p.ConfigService().GetBool("fabric.enabled") + if fabricEnabled { + return errors.Join( + p.Container().Invoke(registerInteropStateDrivers), + ) + } + return nil +} + +func registerInteropStateDrivers(in struct { + dig.In + StateServiceProvider *state.ServiceProvider + Drivers []driver.NamedSSPDriver `group:"ssp-drivers"` +}) { + for _, d := range in.Drivers { + in.StateServiceProvider.RegisterDriver(d) + } } diff --git a/integration/token/fungible/mixed/mixed_test.go b/integration/token/fungible/mixed/mixed_test.go index c24fd8906..e4e29e2c8 100644 --- a/integration/token/fungible/mixed/mixed_test.go +++ b/integration/token/fungible/mixed/mixed_test.go @@ -18,7 +18,7 @@ import ( ) var _ = Describe("EndToEnd", func() { - for _, t := range integration.AllTestTypes { + for _, t := range integration.WebSocketNoReplicationOnly { Describe("Fungible with Auditor ne Issuer", t.Label, func() { ts, selector := newTestSuite(t.CommType, t.ReplicationFactor, "alice", "bob") BeforeEach(ts.Setup) diff --git a/integration/token/interop/dlog/dlog_test.go b/integration/token/interop/dlog/dlog_test.go index d50d83238..0aa3ac06d 100644 --- a/integration/token/interop/dlog/dlog_test.go +++ b/integration/token/interop/dlog/dlog_test.go @@ -13,6 +13,7 @@ import ( "github.com/hyperledger-labs/fabric-token-sdk/integration/nwo/token" token2 "github.com/hyperledger-labs/fabric-token-sdk/integration/token" "github.com/hyperledger-labs/fabric-token-sdk/integration/token/common" + "github.com/hyperledger-labs/fabric-token-sdk/integration/token/common/sdk/fall" "github.com/hyperledger-labs/fabric-token-sdk/integration/token/common/sdk/fdlog" "github.com/hyperledger-labs/fabric-token-sdk/integration/token/common/sdk/fodlog" "github.com/hyperledger-labs/fabric-token-sdk/integration/token/common/sdk/odlog" @@ -60,6 +61,15 @@ var _ = Describe("DLog end to end", func() { It("Performed an htlc based atomic swap", Label("T6"), func() { interop.TestHTLCNoCrossClaimTwoNetworks(ts.II, selector) }) }) } + + for _, t := range integration2.WebSocketWithReplicationOnly { + Describe("Asset Transfer With Two Fabric Networks", t.Label, func() { + ts, selector := newTestSuiteInteropAssetTransfer(t.CommType, t.ReplicationFactor, "alice", "bob") + AfterEach(ts.TearDown) + BeforeEach(ts.Setup) + It("Performed a cross network asset transfer", Label("T7"), func() { interop.TestAssetTransferWithTwoNetworks(ts.II, selector) }) + }) + } }) func newTestSuiteSingleFabric(commType fsc.P2PCommunicationType, factor int, names ...string) (*token2.TestSuite, *token2.ReplicaSelector) { @@ -118,3 +128,15 @@ func newTestSuiteNoCrossClaimOrion(commType fsc.P2PCommunicationType, factor int })) return ts, selector } + +func newTestSuiteInteropAssetTransfer(commType fsc.P2PCommunicationType, factor int, names ...string) (*token2.TestSuite, *token2.ReplicaSelector) { + opts, selector := token2.NewReplicationOptions(factor, names...) + ts := token2.NewTestSuite(opts.SQLConfigs, integration2.ZKATDLogInteropAssetTransfer.StartPortForNode, interop.AssetTransferTopology(common.Opts{ + CommType: commType, + ReplicationOpts: opts, + TokenSDKDriver: "dlog", + SDKs: []api2.SDK{&fall.SDK{}}, + // FSCLogSpec: "token-sdk=debug:fabric-sdk=debug:view-sdk=debug:info", + })) + return ts, selector +} diff --git a/integration/token/interop/fabtoken/fabtoken_test.go b/integration/token/interop/fabtoken/fabtoken_test.go index e3192a665..0bd07b40f 100644 --- a/integration/token/interop/fabtoken/fabtoken_test.go +++ b/integration/token/interop/fabtoken/fabtoken_test.go @@ -13,6 +13,7 @@ import ( "github.com/hyperledger-labs/fabric-token-sdk/integration/nwo/token" token2 "github.com/hyperledger-labs/fabric-token-sdk/integration/token" "github.com/hyperledger-labs/fabric-token-sdk/integration/token/common" + "github.com/hyperledger-labs/fabric-token-sdk/integration/token/common/sdk/fall" "github.com/hyperledger-labs/fabric-token-sdk/integration/token/common/sdk/ffabtoken" "github.com/hyperledger-labs/fabric-token-sdk/integration/token/common/sdk/fofabtoken" "github.com/hyperledger-labs/fabric-token-sdk/integration/token/common/sdk/ofabtoken" @@ -60,6 +61,15 @@ var _ = Describe("FabToken end to end", func() { It("Performed an htlc based atomic swap", Label("T6"), func() { interop.TestHTLCNoCrossClaimTwoNetworks(ts.II, selector) }) }) } + + for _, t := range integration2.WebSocketWithReplicationOnly { + Describe("Asset Transfer With Two Fabric Networks", t.Label, func() { + ts, selector := newTestSuiteInteropAssetTransfer(t.CommType, t.ReplicationFactor, "alice", "bob") + AfterEach(ts.TearDown) + BeforeEach(ts.Setup) + It("Performed a cross network asset transfer", Label("T7"), func() { interop.TestAssetTransferWithTwoNetworks(ts.II, selector) }) + }) + } }) func newTestSuiteSingleFabric(commType fsc.P2PCommunicationType, factor int, names ...string) (*token2.TestSuite, *token2.ReplicaSelector) { @@ -116,3 +126,15 @@ func newTestSuiteNoCrossClaimOrion(commType fsc.P2PCommunicationType, factor int })) return ts, selector } + +func newTestSuiteInteropAssetTransfer(commType fsc.P2PCommunicationType, factor int, names ...string) (*token2.TestSuite, *token2.ReplicaSelector) { + opts, selector := token2.NewReplicationOptions(factor, names...) + ts := token2.NewTestSuite(opts.SQLConfigs, integration2.FabTokenInteropAssetTransfer.StartPortForNode, interop.AssetTransferTopology(common.Opts{ + CommType: commType, + ReplicationOpts: opts, + TokenSDKDriver: "fabtoken", + SDKs: []api2.SDK{&fall.SDK{}}, + // FSCLogSpec: "token-sdk=debug:fabric-sdk=debug:view-sdk=debug:info", + })) + return ts, selector +} diff --git a/integration/token/interop/support.go b/integration/token/interop/support.go index 279b7551e..ff2bd48f0 100644 --- a/integration/token/interop/support.go +++ b/integration/token/interop/support.go @@ -22,6 +22,7 @@ import ( "github.com/hyperledger-labs/fabric-token-sdk/integration/token/fungible/views" views2 "github.com/hyperledger-labs/fabric-token-sdk/integration/token/interop/views" "github.com/hyperledger-labs/fabric-token-sdk/integration/token/interop/views/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/integration/token/interop/views/pledge" "github.com/hyperledger-labs/fabric-token-sdk/token" token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token" . "github.com/onsi/gomega" @@ -441,3 +442,187 @@ func scanWithError(network *integration.Infrastructure, id *token3.NodeReference Expect(err.Error()).To(ContainSubstring(msg)) } } + +func Pledge( + network *integration.Infrastructure, + sender *token3.NodeReference, + wallet, typ string, + amount uint64, + receiver, issuer *token3.NodeReference, + destNetwork string, + deadline time.Duration, + opts ...token.ServiceOption, +) (string, string) { + options, err := token.CompileServiceOptions(opts...) + Expect(err).NotTo(HaveOccurred()) + raw, err := network.Client(sender.ReplicaName()).CallView("transfer.pledge", common.JSONMarshall(&pledge.Pledge{ + OriginTMSID: options.TMSID(), + Amount: amount, + ReclamationDeadline: deadline, + Type: typ, + DestinationNetworkURL: destNetwork, + Issuer: network.Identity(issuer.Id()), + Recipient: network.Identity(receiver.Id()), + OriginWallet: wallet, + })) + Expect(err).NotTo(HaveOccurred()) + info := &pledge.Result{} + common.JSONUnmarshal(raw.([]byte), info) + tmsID := options.TMSID() + common2.CheckFinality(network, sender, info.TxID, &tmsID, false) + return info.TxID, info.PledgeID +} + +func PledgeIDExists(network *integration.Infrastructure, id *token3.NodeReference, pledgeid string, startingTransactionID string, opts ...token.ServiceOption) { + options, err := token.CompileServiceOptions(opts...) + Expect(err).NotTo(HaveOccurred()) + raw, err := network.Client(id.ReplicaName()).CallView("transfer.scan", common.JSONMarshall(&pledge.Scan{ + TMSID: options.TMSID(), + Timeout: 120 * time.Second, + PledgeID: pledgeid, + StartingTransactionID: startingTransactionID, + })) + Expect(err).NotTo(HaveOccurred()) + var res bool + common.JSONUnmarshal(raw.([]byte), &res) + Expect(res).Should(BeTrue()) +} + +func ScanPledgeIDWithError(network *integration.Infrastructure, id *token3.NodeReference, pledgeid string, startingTransactionID string, errorMsgs []string, opts ...token.ServiceOption) { + options, err := token.CompileServiceOptions(opts...) + Expect(err).NotTo(HaveOccurred()) + _, err = network.Client(id.ReplicaName()).CallView("transfer.scan", common.JSONMarshall(&pledge.Scan{ + TMSID: options.TMSID(), + Timeout: 120 * time.Second, + PledgeID: pledgeid, + StartingTransactionID: startingTransactionID, + })) + Expect(err).To(HaveOccurred()) + for _, msg := range errorMsgs { + Expect(err.Error()).To(ContainSubstring(msg)) + } +} + +func Reclaim(network *integration.Infrastructure, sender *token3.NodeReference, wallet string, txid string, opts ...token.ServiceOption) string { + options, err := token.CompileServiceOptions(opts...) + Expect(err).NotTo(HaveOccurred()) + txID, err := network.Client(sender.ReplicaName()).CallView("transfer.reclaim", common.JSONMarshall(&pledge.Reclaim{ // TokenID contains the identifier of the token to be reclaimed. + TokenID: &token2.ID{TxId: txid, Index: 0}, + WalletID: wallet, + TMSID: options.TMSID(), + })) + Expect(err).NotTo(HaveOccurred()) + tmsID := options.TMSID() + common2.CheckFinality(network, sender, common.JSONUnmarshalString(txID), &tmsID, false) + + return common.JSONUnmarshalString(txID) +} + +func ReclaimWithError(network *integration.Infrastructure, sender *token3.NodeReference, wallet string, txid string, opts ...token.ServiceOption) { + options, err := token.CompileServiceOptions(opts...) + Expect(err).NotTo(HaveOccurred()) + _, err = network.Client(sender.ReplicaName()).CallView("transfer.reclaim", common.JSONMarshall(&pledge.Reclaim{ // TokenID contains the identifier of the token to be reclaimed. + TokenID: &token2.ID{TxId: txid, Index: 0}, + WalletID: wallet, + TMSID: options.TMSID(), + })) + Expect(err).To(HaveOccurred()) +} + +func Claim(network *integration.Infrastructure, recipient *token3.NodeReference, issuer *token3.NodeReference, originTokenID *token2.ID) string { + txid, err := network.Client(recipient.ReplicaName()).CallView("transfer.claim", common.JSONMarshall(&pledge.Claim{ + OriginTokenID: originTokenID, + Issuer: issuer.Id(), + })) + Expect(err).NotTo(HaveOccurred()) + common2.CheckFinality(network, recipient, common.JSONUnmarshalString(txid), nil, false) + + return common.JSONUnmarshalString(txid) +} + +func ClaimWithError(network *integration.Infrastructure, recipient *token3.NodeReference, issuer *token3.NodeReference, originTokenID *token2.ID) { + _, err := network.Client(recipient.ReplicaName()).CallView("transfer.claim", common.JSONMarshall(&pledge.Claim{ + OriginTokenID: originTokenID, + Issuer: issuer.Id(), + })) + Expect(err).To(HaveOccurred()) +} + +func RedeemWithTMS(network *integration.Infrastructure, issuer *token3.NodeReference, tokenID *token2.ID, opts ...token.ServiceOption) string { + options, err := token.CompileServiceOptions(opts...) + Expect(err).NotTo(HaveOccurred()) + txID, err := network.Client(issuer.ReplicaName()).CallView("transfer.redeem", common.JSONMarshall(&pledge.Redeem{ + TokenID: tokenID, + TMSID: options.TMSID(), + })) + Expect(err).NotTo(HaveOccurred()) + tmsID := options.TMSID() + common2.CheckFinality(network, issuer, common.JSONUnmarshalString(txID), &tmsID, false) + + return common.JSONUnmarshalString(txID) +} + +func RedeemWithTMSAndError(network *integration.Infrastructure, issuer *token3.NodeReference, tokenID *token2.ID, opt token.ServiceOption, errorMsgs ...string) { + options, err := token.CompileServiceOptions(opt) + Expect(err).NotTo(HaveOccurred()) + _, err = network.Client(issuer.ReplicaName()).CallView("transfer.redeem", common.JSONMarshall(&pledge.Redeem{ + TokenID: tokenID, + TMSID: options.TMSID(), + })) + Expect(err).To(HaveOccurred()) + for _, msg := range errorMsgs { + Expect(err.Error()).To(ContainSubstring(msg)) + } +} + +func FastTransferPledgeClaim( + network *integration.Infrastructure, + sender *token3.NodeReference, + wallet, typ string, + amount uint64, + receiver, issuer *token3.NodeReference, + destNetwork string, + deadline time.Duration, + opts ...token.ServiceOption, +) { + options, err := token.CompileServiceOptions(opts...) + Expect(err).NotTo(HaveOccurred()) + + _, err = network.Client(sender.ReplicaName()).CallView("transfer.fastTransfer", common.JSONMarshall(&pledge.FastPledgeClaim{ + OriginTMSID: options.TMSID(), + Amount: amount, + ReclamationDeadline: deadline, + Type: typ, + DestinationNetworkURL: destNetwork, + Issuer: network.Identity(issuer.Id()), + Recipient: network.Identity(receiver.Id()), + OriginWallet: wallet, + })) + Expect(err).NotTo(HaveOccurred()) +} + +func FastTransferPledgeReclaim( + network *integration.Infrastructure, + sender *token3.NodeReference, + wallet, typ string, + amount uint64, + receiver, issuer *token3.NodeReference, + destNetwork string, + deadline time.Duration, + opts ...token.ServiceOption, +) { + options, err := token.CompileServiceOptions(opts...) + Expect(err).NotTo(HaveOccurred()) + + _, err = network.Client(sender.ReplicaName()).CallView("transfer.fastPledgeReclaim", common.JSONMarshall(&pledge.FastPledgeReClaim{ + OriginTMSID: options.TMSID(), + Amount: amount, + ReclamationDeadline: deadline, + Type: typ, + DestinationNetworkURL: destNetwork, + Issuer: network.Identity(issuer.Id()), + Recipient: network.Identity(receiver.Id()), + OriginWallet: wallet, + })) + Expect(err).NotTo(HaveOccurred()) +} diff --git a/integration/token/interop/tests.go b/integration/token/interop/tests.go index 5f33fb638..2a1e12c8d 100644 --- a/integration/token/interop/tests.go +++ b/integration/token/interop/tests.go @@ -20,6 +20,8 @@ import ( "github.com/hyperledger-labs/fabric-token-sdk/token" auditor2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/auditor" "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state/fabric" + token3 "github.com/hyperledger-labs/fabric-token-sdk/token/token" . "github.com/onsi/gomega" "github.com/pkg/errors" ) @@ -357,3 +359,143 @@ func TestFastExchange(network *integration.Infrastructure, sel *token2.ReplicaSe CheckBalance(network, sel.Get("alice"), "", "USD", 10, token.WithTMSID(beta)) Eventually(CheckBalanceReturnError).WithArguments(network, sel.Get("bob"), "", "USD", uint64(20), token.WithTMSID(beta)).WithTimeout(1 * time.Minute).WithPolling(15 * time.Second).Should(Succeed()) } + +func TestAssetTransferWithTwoNetworks(network *integration.Infrastructure, sel *token2.ReplicaSelector) { + issuerAlpha := sel.Get("issuerAlpha") + issuerBeta := sel.Get("issuerBeta") + alice := sel.Get("alice") + bob := sel.Get("bob") + auditor := sel.Get("auditor") + + // give some time to the nodes to get the public parameters + time.Sleep(10 * time.Second) + + alpha := token.TMSID{ + Network: "alpha", + Channel: "testchannel", + Namespace: "tns", + } + beta := token.TMSID{ + Network: "beta", + Channel: "testchannel", + Namespace: "tns", + } + + alphaURL := fabric.FabricURL(alpha) + betaURL := fabric.FabricURL(beta) + + RegisterAuditor(network, token.WithTMSID(alpha)) + RegisterAuditor(network, token.WithTMSID(beta)) + + IssueCashWithTMS(network, alpha, issuerAlpha, "", "USD", 50, alice, auditor) + IssueCashWithTMS(network, alpha, issuerAlpha, "", "EUR", 10, alice, auditor) + CheckBalance(network, alice, "", "USD", 50) + CheckBalance(network, alice, "", "EUR", 10) + + // Pledge + Claim + txid, pledgeid := Pledge(network, alice, "", "USD", 50, bob, issuerAlpha, betaURL, time.Minute*1, token.WithTMSID(alpha)) + CheckBalance(network, alice, "", "USD", 0) + CheckBalance(network, bob, "", "USD", 0) + + PledgeIDExists(network, alice, pledgeid, txid, token.WithTMSID(alpha)) + + Claim(network, bob, issuerBeta, &token3.ID{TxId: txid, Index: 0}) + CheckBalance(network, alice, "", "USD", 0) + CheckBalance(network, bob, "", "USD", 50) + + time.Sleep(time.Minute * 1) + RedeemWithTMS(network, issuerAlpha, &token3.ID{TxId: txid, Index: 0}, token.WithTMSID(alpha)) + CheckBalance(network, alice, "", "USD", 0) + CheckBalance(network, bob, "", "USD", 50) + + // Pledge + Reclaim + + IssueCashWithTMS(network, alpha, issuerAlpha, "", "USD", 50, alice, auditor) + CheckBalance(network, alice, "", "USD", 50, token.WithTMSID(alpha)) + txid, _ = Pledge(network, alice, "", "USD", 50, bob, issuerAlpha, betaURL, time.Second*10, token.WithTMSID(alpha)) + + time.Sleep(time.Second * 15) + Reclaim(network, alice, "", txid, token.WithTMSID(alpha)) + CheckBalance(network, alice, "", "USD", 50, token.WithTMSID(alpha)) + CheckBalance(network, bob, "", "USD", 50) + + RedeemWithTMSAndError(network, issuerAlpha, &token3.ID{TxId: txid, Index: 0}, token.WithTMSID(alpha)) + CheckBalance(network, alice, "", "USD", 50, token.WithTMSID(alpha)) + CheckBalance(network, bob, "", "USD", 50) + + ScanPledgeIDWithError(network, alice, pledgeid, txid, []string{"timeout reached"}, token.WithTMSID(alpha)) + + // Try to reclaim again + + ReclaimWithError(network, alice, "", txid, token.WithTMSID(alpha)) + CheckBalance(network, alice, "", "USD", 50, token.WithTMSID(alpha)) + CheckBalance(network, bob, "", "USD", 50) + + // Try to claim after reclaim + + ClaimWithError(network, bob, issuerBeta, &token3.ID{TxId: txid, Index: 0}) + CheckBalance(network, alice, "", "USD", 50, token.WithTMSID(alpha)) + CheckBalance(network, bob, "", "USD", 50) + + // Try to reclaim after claim + + txid, _ = Pledge(network, alice, "", "USD", 10, bob, issuerAlpha, betaURL, time.Minute*1, token.WithTMSID(alpha)) + CheckBalance(network, alice, "", "USD", 40) + CheckBalance(network, bob, "", "USD", 50) + + Claim(network, bob, issuerBeta, &token3.ID{TxId: txid, Index: 0}) + CheckBalance(network, alice, "", "USD", 40) + CheckBalance(network, bob, "", "USD", 60) + + time.Sleep(time.Minute * 1) + + ReclaimWithError(network, alice, "", txid, token.WithTMSID(alpha)) + CheckBalance(network, alice, "", "USD", 40) + CheckBalance(network, bob, "", "USD", 60) + + // Try to claim after claim + + txid, pledgeid = Pledge(network, bob, "", "USD", 5, alice, issuerBeta, alphaURL, time.Minute*1, token.WithTMSID(beta)) + CheckBalance(network, alice, "", "USD", 40) + CheckBalance(network, bob, "", "USD", 55) + + PledgeIDExists(network, bob, pledgeid, txid, token.WithTMSID(beta)) + + Claim(network, alice, issuerAlpha, &token3.ID{TxId: txid, Index: 0}) + CheckBalance(network, alice, "", "USD", 45) + CheckBalance(network, bob, "", "USD", 55) + + ClaimWithError(network, alice, issuerAlpha, &token3.ID{TxId: txid, Index: 0}) + CheckBalance(network, alice, "", "USD", 45) + CheckBalance(network, bob, "", "USD", 55) + + time.Sleep(1 * time.Minute) + RedeemWithTMS(network, issuerBeta, &token3.ID{TxId: txid, Index: 0}, token.WithTMSID(beta)) + CheckBalance(network, alice, "", "USD", 45) + CheckBalance(network, bob, "", "USD", 55) + + // Try to redeem again + RedeemWithTMSAndError(network, issuerBeta, &token3.ID{TxId: txid, Index: 0}, token.WithTMSID(beta), "failed to retrieve pledged token during redeem") + CheckBalance(network, alice, "", "USD", 45) + CheckBalance(network, bob, "", "USD", 55) + + // Try to claim or reclaim without pledging + + ClaimWithError(network, alice, issuerAlpha, &token3.ID{TxId: "", Index: 0}) + CheckBalance(network, alice, "", "USD", 45) + CheckBalance(network, bob, "", "USD", 55) + + ReclaimWithError(network, alice, "", "", token.WithTMSID(alpha)) + CheckBalance(network, alice, "", "USD", 45) + CheckBalance(network, bob, "", "USD", 55) + + // Fast Pledge + Claim + FastTransferPledgeClaim(network, alice, "", "USD", 10, bob, issuerAlpha, betaURL, time.Minute*1, token.WithTMSID(alpha)) + CheckBalance(network, alice, "", "USD", 35) + CheckBalance(network, bob, "", "USD", 65) + + // Fast Pledge + Reclaim + FastTransferPledgeReclaim(network, alice, "", "USD", 10, bob, issuerAlpha, betaURL, time.Second*5, token.WithTMSID(alpha)) + CheckBalance(network, alice, "", "USD", 35) + CheckBalance(network, bob, "", "USD", 65) +} diff --git a/integration/token/interop/topology.go b/integration/token/interop/topology.go index fe23b79f8..92f6250cf 100644 --- a/integration/token/interop/topology.go +++ b/integration/token/interop/topology.go @@ -12,6 +12,7 @@ import ( "github.com/hyperledger-labs/fabric-smart-client/integration/nwo/fsc" "github.com/hyperledger-labs/fabric-smart-client/integration/nwo/fsc/node" "github.com/hyperledger-labs/fabric-smart-client/integration/nwo/orion" + "github.com/hyperledger-labs/fabric-smart-client/integration/nwo/weaver" "github.com/hyperledger-labs/fabric-token-sdk/integration/nwo/token" fabric2 "github.com/hyperledger-labs/fabric-token-sdk/integration/nwo/token/fabric" orion2 "github.com/hyperledger-labs/fabric-token-sdk/integration/nwo/token/orion" @@ -20,6 +21,7 @@ import ( "github.com/hyperledger-labs/fabric-token-sdk/integration/token/fungible/views" views2 "github.com/hyperledger-labs/fabric-token-sdk/integration/token/interop/views" "github.com/hyperledger-labs/fabric-token-sdk/integration/token/interop/views/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/integration/token/interop/views/pledge" ) func HTLCSingleFabricNetworkTopology(opts common.Opts) []api.Topology { @@ -31,7 +33,7 @@ func HTLCSingleFabricNetworkTopology(opts common.Opts) []api.Topology { // FSC fscTopology := fsc.NewTopology() - // fscTopology.SetLogging("token-sdk=debug:fabric-sdk=debug:info", "") + fscTopology.SetLogging(opts.FSCLogSpec, "") fscTopology.P2PCommunicationType = opts.CommType addIssuer(fscTopology). @@ -68,7 +70,7 @@ func HTLCSingleOrionNetworkTopology(opts common.Opts) []api.Topology { // FSC fscTopology := fsc.NewTopology() - // fscTopology.SetLogging("debug", "") + fscTopology.SetLogging(opts.FSCLogSpec, "") fscTopology.P2PCommunicationType = opts.CommType addIssuer(fscTopology). @@ -116,7 +118,7 @@ func HTLCTwoFabricNetworksTopology(opts common.Opts) []api.Topology { // FSC fscTopology := fsc.NewTopology() - // fscTopology.SetLogging("debug", "") + fscTopology.SetLogging(opts.FSCLogSpec, "") fscTopology.P2PCommunicationType = opts.CommType addIssuer(fscTopology). @@ -245,7 +247,7 @@ func HTLCNoCrossClaimWithOrionTopology(opts common.Opts) []api.Topology { // FSC fscTopology := fsc.NewTopology() - // fscTopology.SetLogging("db.driver.badger=info:debug", "") + fscTopology.SetLogging(opts.FSCLogSpec, "") fscTopology.P2PCommunicationType = opts.CommType addIssuer(fscTopology). @@ -306,6 +308,127 @@ func HTLCNoCrossClaimWithOrionTopology(opts common.Opts) []api.Topology { return []api.Topology{f1Topology, orionTopology, tokenTopology, fscTopology} } +func AssetTransferTopology(opts common.Opts) []api.Topology { + // Define two Fabric topologies + f1Topology := fabric.NewTopologyWithName("alpha").SetDefault() + f1Topology.EnableIdemix() + f1Topology.AddOrganizationsByName("Org1", "Org2") + f1Topology.SetNamespaceApproverOrgs("Org1") + + f2Topology := fabric.NewTopologyWithName("beta") + f2Topology.EnableIdemix() + f2Topology.AddOrganizationsByName("Org3", "Org4") + f2Topology.SetNamespaceApproverOrgs("Org3") + + // FSC + fscTopology := fsc.NewTopology() + fscTopology.SetLogging(opts.FSCLogSpec, "") + + wTopology := weaver.NewTopology() + wTopology.AddRelayServer(f1Topology, "Org1").AddFabricNetwork(f2Topology) + wTopology.AddRelayServer(f2Topology, "Org3").AddFabricNetwork(f1Topology) + + issuerAlpha := fscTopology.AddNodeByName("issuerAlpha").AddOptions( + fabric.WithNetworkOrganization("alpha", "Org1"), + fabric.WithAnonymousIdentity(), + token.WithIssuerIdentity("issuer.id1", false), + token.WithOwnerIdentity("issuer.id1.owner"), + ) + issuerAlpha.RegisterViewFactory("issue", &views2.IssueCashViewFactory{}) + issuerAlpha.RegisterViewFactory("balance", &views2.BalanceViewFactory{}) + issuerAlpha.RegisterViewFactory("transfer.redeem", &pledge.RedeemViewFactory{}) + issuerAlpha.RegisterResponder(&pledge.IssuerResponderView{}, &pledge.View{}) + issuerAlpha.RegisterResponder(&pledge.IssuerResponderView{}, &pledge.FastPledgeClaimInitiatorView{}) + issuerAlpha.RegisterResponder(&pledge.IssuerResponderView{}, &pledge.FastPledgeReClaimInitiatorView{}) + issuerAlpha.RegisterResponder(&pledge.ReclaimIssuerResponderView{}, &pledge.ReclaimInitiatorView{}) + issuerAlpha.RegisterResponder(&pledge.ClaimIssuerView{}, &pledge.ClaimInitiatorView{}) + issuerAlpha.RegisterResponder(&pledge.ClaimIssuerView{}, &pledge.FastPledgeClaimResponderView{}) + issuerAlpha.RegisterResponder(&pledge.ClaimIssuerView{}, &pledge.FastPledgeReClaimResponderView{}) + issuerAlpha.RegisterViewFactory("TxFinality", &views3.TxFinalityViewFactory{}) + + issuerBeta := fscTopology.AddNodeByName("issuerBeta").AddOptions( + fabric.WithNetworkOrganization("beta", "Org3"), + fabric.WithAnonymousIdentity(), + token.WithIssuerIdentity("issuer.id2", false), + token.WithOwnerIdentity("issuer.id2.owner"), + ) + issuerBeta.RegisterViewFactory("issue", &views2.IssueCashViewFactory{}) + issuerBeta.RegisterViewFactory("balance", &views2.BalanceViewFactory{}) + issuerBeta.RegisterViewFactory("transfer.redeem", &pledge.RedeemViewFactory{}) + issuerBeta.RegisterResponder(&pledge.IssuerResponderView{}, &pledge.View{}) + issuerBeta.RegisterResponder(&pledge.IssuerResponderView{}, &pledge.FastPledgeClaimInitiatorView{}) + issuerBeta.RegisterResponder(&pledge.IssuerResponderView{}, &pledge.FastPledgeReClaimInitiatorView{}) + issuerBeta.RegisterResponder(&pledge.ReclaimIssuerResponderView{}, &pledge.ReclaimInitiatorView{}) + issuerBeta.RegisterResponder(&pledge.ClaimIssuerView{}, &pledge.ClaimInitiatorView{}) + issuerBeta.RegisterResponder(&pledge.ClaimIssuerView{}, &pledge.FastPledgeClaimResponderView{}) + issuerBeta.RegisterResponder(&pledge.ClaimIssuerView{}, &pledge.FastPledgeReClaimResponderView{}) + issuerBeta.RegisterViewFactory("TxFinality", &views3.TxFinalityViewFactory{}) + + auditor := fscTopology.AddNodeByName("auditor").AddOptions( + fabric.WithNetworkOrganization("alpha", "Org1"), + fabric.WithNetworkOrganization("beta", "Org3"), + fabric.WithAnonymousIdentity(), + token.WithAuditorIdentity(false), + ) + auditor.RegisterViewFactory("registerAuditor", &views2.RegisterAuditorViewFactory{}) + auditor.RegisterViewFactory("balance", &views2.BalanceViewFactory{}) + auditor.RegisterViewFactory("TxFinality", &views3.TxFinalityViewFactory{}) + + alice := fscTopology.AddNodeByName("alice").AddOptions( + fabric.WithNetworkOrganization("alpha", "Org2"), + fabric.WithAnonymousIdentity(), + token.WithOwnerIdentity("alice.id1"), + ) + alice.RegisterResponder(&views2.AcceptCashView{}, &views2.IssueCashView{}) + alice.RegisterViewFactory("balance", &views2.BalanceViewFactory{}) + alice.RegisterViewFactory("transfer.claim", &pledge.ClaimInitiatorViewFactory{}) + alice.RegisterViewFactory("transfer.pledge", &pledge.ViewFactory{}) + alice.RegisterViewFactory("transfer.reclaim", &pledge.ReclaimViewFactory{}) + alice.RegisterViewFactory("transfer.fastTransfer", &pledge.FastPledgeClaimInitiatorViewFactory{}) + alice.RegisterViewFactory("transfer.fastPledgeReclaim", &pledge.FastPledgeReClaimInitiatorViewFactory{}) + alice.RegisterViewFactory("transfer.scan", &pledge.ScanViewFactory{}) + alice.RegisterResponder(&pledge.RecipientResponderView{}, &pledge.View{}) + alice.RegisterResponder(&pledge.FastPledgeClaimResponderView{}, &pledge.FastPledgeClaimInitiatorView{}) + alice.RegisterResponder(&pledge.FastPledgeReClaimResponderView{}, &pledge.FastPledgeReClaimInitiatorView{}) + alice.RegisterViewFactory("TxFinality", &views3.TxFinalityViewFactory{}) + + bob := fscTopology.AddNodeByName("bob").AddOptions( + fabric.WithNetworkOrganization("beta", "Org4"), + fabric.WithAnonymousIdentity(), + token.WithOwnerIdentity("bob.id1"), + ) + bob.RegisterResponder(&views2.AcceptCashView{}, &views2.IssueCashView{}) + bob.RegisterViewFactory("balance", &views2.BalanceViewFactory{}) + bob.RegisterViewFactory("transfer.claim", &pledge.ClaimInitiatorViewFactory{}) + bob.RegisterViewFactory("transfer.pledge", &pledge.ViewFactory{}) + bob.RegisterViewFactory("transfer.reclaim", &pledge.ReclaimViewFactory{}) + bob.RegisterViewFactory("transfer.fastTransfer", &pledge.FastPledgeClaimInitiatorViewFactory{}) + bob.RegisterViewFactory("transfer.fastPledgeReclaim", &pledge.FastPledgeReClaimInitiatorViewFactory{}) + bob.RegisterViewFactory("transfer.scan", &pledge.ScanViewFactory{}) + bob.RegisterResponder(&pledge.RecipientResponderView{}, &pledge.View{}) + bob.RegisterResponder(&pledge.FastPledgeClaimResponderView{}, &pledge.FastPledgeClaimInitiatorView{}) + bob.RegisterResponder(&pledge.FastPledgeReClaimResponderView{}, &pledge.FastPledgeReClaimInitiatorView{}) + bob.RegisterViewFactory("TxFinality", &views3.TxFinalityViewFactory{}) + + tokenTopology := token.NewTopology() + tms := tokenTopology.AddTMS(fscTopology.ListNodes("auditor", "issuerAlpha", "alice"), f1Topology, f1Topology.Channels[0].Name, opts.TokenSDKDriver) + common.SetDefaultParams(opts.TokenSDKDriver, tms, true) + fabric2.SetOrgs(tms, "Org1") + tms.AddAuditor(auditor) + + tms = tokenTopology.AddTMS(fscTopology.ListNodes("auditor", "issuerBeta", "bob"), f2Topology, f2Topology.Channels[0].Name, opts.TokenSDKDriver) + common.SetDefaultParams(opts.TokenSDKDriver, tms, true) + fabric2.SetOrgs(tms, "Org3") + tms.AddAuditor(auditor) + + // Add SDKs to FSC Nodes + for _, sdk := range opts.SDKs { + fscTopology.AddSDK(sdk) + } + + return []api.Topology{f1Topology, f2Topology, tokenTopology, wTopology, fscTopology} +} + func addIssuer(fscTopology *fsc.Topology) *node.Node { return fscTopology.AddNodeByName("issuer"). AddOptions( diff --git a/integration/token/interop/views/auditor.go b/integration/token/interop/views/auditor.go index 153f85324..61adf1971 100644 --- a/integration/token/interop/views/auditor.go +++ b/integration/token/interop/views/auditor.go @@ -14,7 +14,7 @@ import ( "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/assert" "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" "github.com/hyperledger-labs/fabric-token-sdk/token" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop" "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" ) @@ -66,28 +66,40 @@ func (a *AuditView) Call(context view.Context) (interface{}, error) { } for i := 0; i < inputs.Count(); i++ { - input, err := htlc.ToInput(inputs.At(i)) + input, err := interop.ToInput(inputs.At(i)) assert.NoError(err, "cannot get htlc input wrapper") - if !input.IsHTLC() { - continue + + switch { + case input.IsHTLC(): + // check script details + script, err := input.HTLC() + assert.NoError(err, "cannot get htlc script from output") + assert.True(len(script.HashInfo.Hash) > 0, "hash is not set") + assert.NoError(script.WellFormedness(), "htlc script is not valid") + case input.IsPledge(): + // check script details + script, err := input.Pledge() + assert.NoError(err, "cannot get pledge script from input") + assert.NoError(script.WellFormedness(), "pledge script is not valid") } - // check script details, for example make sure the hash is set - script, err := input.Script() - assert.NoError(err, "cannot get htlc script from input") - assert.True(len(script.HashInfo.Hash) > 0, "hash is not set") } now := time.Now() for i := 0; i < outputs.Count(); i++ { - output, err := htlc.ToOutput(outputs.At(i)) + output, err := interop.ToOutput(outputs.At(i)) assert.NoError(err, "cannot get htlc output wrapper") - if !output.IsHTLC() { - continue + switch { + case output.IsHTLC(): + // check script details + script, err := output.HTLC() + assert.NoError(err, "cannot get htlc script from output") + assert.NoError(script.Validate(now), "script is not valid") + case output.IsPledge(): + // check script details + script, err := output.Pledge() + assert.NoError(err, "cannot get pledge script from input") + assert.NoError(script.Validate(now), "script is not valid") } - // check script details - script, err := output.Script() - assert.NoError(err, "cannot get htlc script from output") - assert.NoError(script.Validate(now), "script is not valid") } return context.RunView(ttx.NewAuditApproveView(w, tx)) diff --git a/integration/token/interop/views/pledge/claim.go b/integration/token/interop/views/pledge/claim.go new file mode 100644 index 000000000..2ff2f6723 --- /dev/null +++ b/integration/token/interop/views/pledge/claim.go @@ -0,0 +1,151 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric" + view3 "github.com/hyperledger-labs/fabric-smart-client/platform/view" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/assert" + view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/view" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" +) + +// Claim contains the input information to claim a token. +type Claim struct { + // OriginTokenID is the identifier of the pledged token in the origin network + OriginTokenID *token.ID + // Issuer is the identity of the issuer in the destination network + Issuer string +} + +// ClaimInitiatorView is the view of the initiator of the claim (Bob) +// Claim a token that has already been pledged, but not yet claimed by someone else, reclaimed by the owner or redeemed by the issuer of the network. +type ClaimInitiatorView struct { + *Claim +} + +func (c *ClaimInitiatorView) Call(context view.Context) (interface{}, error) { + // Retrieve proof of existence of the passed token id + pledgeInfo, err := pledge.Vault(context).PledgeByTokenID(c.OriginTokenID) + assert.NoError(err, "failed getting pledge") + assert.NotNil(pledgeInfo, "expected one pledge, got nil") + + logger.Debugf("request proof of existence") + proof, err := pledge.RequestProofOfExistence(context, pledgeInfo) + assert.NoError(err, "failed to retrieve a valid proof of existence") + assert.NotNil(proof) + + // Request claim to the issuer + wallet, err := pledge.MyOwnerWallet(context) + assert.NoError(err, "failed getting my owner wallet") + me, err := wallet.GetRecipientIdentity() + assert.NoError(err, "failed getting recipient identity from my owner wallet") + + // Contact the issuer, present the pledge proof, and ask to initiate the issue process + fns, err := fabric.GetDefaultFNS(context) + assert.NoError(err, "failed getting default fns") + + session, err := pledge.RequestClaim( + context, + fns.IdentityProvider().Identity(c.Issuer), + pledgeInfo, + me, + proof, + ) + assert.NoError(err, "failed requesting a claim from the issuer") + + // Now we have an inversion of roles. + // The issuer becomes the initiator of the issue transaction, + // and this node is the responder + return view2.AsResponder(context, session, + func(context view.Context) (interface{}, error) { + // At some point, the recipient receives the token transaction that in the meantime has been assembled + tx, err := pledge.ReceiveTransaction(context) + assert.NoError(err, "failed to receive transaction") + + // The recipient can perform any check on the transaction + outputs, err := tx.Outputs() + assert.NoError(err, "failed getting outputs") + assert.True(outputs.Count() > 0) + assert.True(outputs.ByRecipient(me).Count() > 0) + output := outputs.At(0) + assert.NotNil(output, "failed getting the output") + assert.NoError(err, "failed parsing quantity") + assert.Equal(pledgeInfo.Amount, output.Quantity.ToBigInt().Uint64()) + assert.Equal(pledgeInfo.TokenType, output.Type) + assert.Equal(me, output.Owner) + + // If everything is fine, the recipient accepts and sends back her signature. + _, err = context.RunView(pledge.NewAcceptView(tx)) + assert.NoError(err, "failed to accept the claim transaction") + + // Before completing, the recipient waits for finality of the transaction + _, err = context.RunView(pledge.NewFinalityView(tx)) + assert.NoError(err, "the claim transaction was not committed") + + // Delete pledges + err = pledge.Vault(context).Delete([]*pledge.Info{pledgeInfo}) + assert.NoError(err, "failed deleting pledges") + + return tx.ID(), nil + }, + ) +} + +type ClaimInitiatorViewFactory struct{} + +func (c *ClaimInitiatorViewFactory) NewView(in []byte) (view.View, error) { + f := &ClaimInitiatorView{Claim: &Claim{}} + err := json.Unmarshal(in, f.Claim) + assert.NoError(err, "failed unmarshalling input") + + return f, nil +} + +// ClaimIssuerView is the view of the issuer responding to the claim interactive protocol. +type ClaimIssuerView struct{} + +func (c *ClaimIssuerView) Call(context view.Context) (interface{}, error) { + // Receive claim request + req, err := pledge.ReceiveClaimRequest(context) + assert.NoError(err, "failed to receive claim request") + + // Validate and check Proof + err = pledge.ValidateClaimRequest(context, req) + assert.NoError(err, "failed validating claim request") + logger.Debugf("claim request valid, preparing claim transaction [%s]", err) + + // At this point, the issuer is ready to prepare the token transaction. + tx, err := pledge.NewAnonymousTransaction( + context, + ttx.WithAuditor(view3.GetIdentityProvider(context).Identity("auditor")), + ) + assert.NoError(err, "failed creating a new transaction") + + // The issuer adds a new claim operation to the transaction following the instruction received. + wallet, err := pledge.GetIssuerWallet(context, "") + assert.NoError(err, "failed to get issuer's wallet") + + err = tx.Claim(wallet, req.TokenType, req.Quantity, req.Recipient, req.OriginTokenID, req.OriginNetwork, req.PledgeProof) + assert.NoError(err, "failed adding a claim action") + + // The issuer is ready to collect all the required signatures. + // Invoke the Token Chaincode to collect endorsements on the Token Request and prepare the relative Fabric transaction. + _, err = context.RunView(pledge.NewCollectEndorsementsView(tx)) + assert.NoError(err, "failed to collect endorsements on claim transaction") + + // Last but not least, the issuer sends the transaction for ordering and waits for transaction finality. + _, err = context.RunView(pledge.NewOrderingAndFinalityView(tx)) + assert.NoError(err, "failed to commit claim transaction") + + return tx.ID(), nil +} diff --git a/integration/token/interop/views/pledge/fast_pledge.go b/integration/token/interop/views/pledge/fast_pledge.go new file mode 100644 index 000000000..8bc863dae --- /dev/null +++ b/integration/token/interop/views/pledge/fast_pledge.go @@ -0,0 +1,328 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + "time" + + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric" + view3 "github.com/hyperledger-labs/fabric-smart-client/platform/view" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/assert" + view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/view" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" + token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" +) + +// FastPledgeClaim contains the input information for a pledge+claim +type FastPledgeClaim struct { + // OriginTMSID identifies the TMS to use to perform the token operation + OriginTMSID token.TMSID + // OriginWallet is the identifier of the wallet that owns the tokens to transfer in the origin network + OriginWallet string + // Type of tokens to transfer + Type string + // Amount to transfer + Amount uint64 + // Issuer is the identity of the issuer's FSC node + Issuer view.Identity + // Recipient is the identity of the recipient's FSC node + Recipient view.Identity + // DestinationNetworkURL is the destination network's url to transfer the token to + DestinationNetworkURL string + // ReclamationDeadline is the time after which we can reclaim the funds in case they were not transferred + ReclamationDeadline time.Duration +} + +type FastPledgeClaimInitiatorViewFactory struct{} + +func (f *FastPledgeClaimInitiatorViewFactory) NewView(in []byte) (view.View, error) { + v := &FastPledgeClaimInitiatorView{FastPledgeClaim: &FastPledgeClaim{}} + err := json.Unmarshal(in, v.FastPledgeClaim) + assert.NoError(err, "failed unmarshalling input") + + return v, nil +} + +type FastPledgeClaimInitiatorView struct { + *FastPledgeClaim +} + +func (v *FastPledgeClaimInitiatorView) Call(context view.Context) (interface{}, error) { + // Collect recipient's token-sdk identity + recipient, err := pledge.RequestPledgeRecipientIdentity(context, v.Recipient, v.DestinationNetworkURL, token.WithTMSID(v.OriginTMSID)) + assert.NoError(err, "failed getting recipient identity") + + // Collect issuer's token-sdk identity + issuer, err := pledge.RequestRecipientIdentity(context, v.Issuer, token.WithTMSID(v.OriginTMSID)) + assert.NoError(err, "failed getting recipient identity") + + // Create a new transaction + tx, err := pledge.NewAnonymousTransaction( + context, + ttx.WithAuditor(view3.GetIdentityProvider(context).Identity("auditor")), + ttx.WithTMSID(v.OriginTMSID), + ) + assert.NoError(err, "failed created a new transaction") + + // The sender will select tokens owned by this wallet + senderWallet := pledge.GetWallet(context, v.OriginWallet, token.WithTMSID(v.OriginTMSID)) + assert.NotNil(senderWallet, "sender wallet [%s] not found", v.OriginWallet) + + _, err = tx.Pledge(senderWallet, v.DestinationNetworkURL, v.ReclamationDeadline, recipient, issuer, v.Type, v.Amount) + assert.NoError(err, "failed pledging") + + // Collect signatures + _, err = context.RunView(pledge.NewCollectEndorsementsView(tx)) + assert.NoError(err, "failed to sign pledge transaction") + + // Last but not least, the issuer sends the transaction for ordering and waits for transaction finality. + _, err = context.RunView(pledge.NewOrderingAndFinalityView(tx)) + assert.NoError(err, "failed to commit issue transaction") + + // Inform the recipient of the pledge, + // recall that the recipient might be aware of only the other network + _, err = context.RunView(pledge.NewDistributePledgeInfoView(tx)) + assert.NoError(err, "failed to send the pledge info") + + time.Sleep(v.ReclamationDeadline) + + // Request proof of non-existence for the passed token, + // we expect the token to exist in the destination network + w, err := pledge.GetOwnerWallet(context, v.OriginWallet, token.WithTMSID(v.OriginTMSID)) + assert.NoError(err, "failed to retrieve wallet of owner during reclaim") + + tokenID := &token2.ID{TxId: tx.ID()} + _, script, err := pledge.Wallet(context, w).GetPledgedToken(tokenID) + assert.NoError(err, "failed to retrieve token to be reclaimed") + assert.False(time.Now().Before(script.Deadline), "cannot reclaim token yet; deadline has not elapsed yet") + + logger.Debugf("request proof of non-existence") + _, err = pledge.RequestProofOfNonExistence(context, tokenID, v.OriginTMSID, script) + assert.Error(err, "retrieve proof of non-existence should fail") + assert.Equal(state.TokenExistsError, errors.Cause(err), "token should exist") + + return nil, nil +} + +type FastPledgeClaimResponderView struct{} + +func (v *FastPledgeClaimResponderView) Call(context view.Context) (interface{}, error) { + me, err := pledge.RespondRequestPledgeRecipientIdentity(context) + assert.NoError(err, "failed to respond to identity request") + + // At some point, the recipient receives the pledge info + pledgeInfo, err := pledge.ReceivePledgeInfo(context) + assert.NoError(err, "failed to receive pledge info") + + // Perform any check that is needed to validate the pledge. + logger.Debugf("The pledge info is %v", pledgeInfo) + assert.Equal(me, pledgeInfo.Script.Recipient, "recipient is different [%s]!=[%s]", me, pledgeInfo.Script.Recipient) + + // Store the pledge and send a notification back + _, err = context.RunView(pledge.NewAcceptPledgeInfoView(pledgeInfo)) + assert.NoError(err, "failed accepting pledge info") + + // Retrieve proof of existence of the passed token id + pledgeInfo, err = pledge.Vault(context).PledgeByTokenID(pledgeInfo.TokenID) + assert.NoError(err, "failed getting pledge") + assert.NotNil(pledgeInfo, "expected one pledge, got nil") + + logger.Debugf("request proof of existence") + proof, err := pledge.RequestProofOfExistence(context, pledgeInfo) + assert.NoError(err, "failed to retrieve a valid proof of existence") + assert.NotNil(proof) + + // Request claim to the issuer + logger.Debugf("Request claim to the issuer") + wallet, err := pledge.MyOwnerWallet(context) + assert.NoError(err, "failed getting my owner wallet") + me, err = wallet.GetRecipientIdentity() + assert.NoError(err, "failed getting recipient identity from my owner wallet") + + var session view.Session + _, err = view2.AsInitiatorCall(context, v, func(context view.Context) (interface{}, error) { + fns, err := fabric.GetDefaultFNS(context) + assert.NoError(err, "failed getting default fns") + session, err = pledge.RequestClaim( + context, + fns.IdentityProvider().Identity("issuerBeta"), // TODO get issuer + pledgeInfo, + me, + proof, + ) + assert.NoError(err, "failed requesting a claim from the issuer") + return nil, nil + }) + assert.NoError(err, "failed to request claim") + + return view2.AsResponder(context, session, + func(context view.Context) (interface{}, error) { + logger.Debugf("Respond to the issuer...") + + // At some point, the recipient receives the token transaction that in the meantime has been assembled + tx, err := pledge.ReceiveTransaction(context) + assert.NoError(err, "failed to receive transaction") + + // The recipient can perform any check on the transaction + outputs, err := tx.Outputs() + assert.NoError(err, "failed getting outputs") + assert.True(outputs.Count() > 0) + assert.True(outputs.ByRecipient(me).Count() > 0) + output := outputs.At(0) + assert.NotNil(output, "failed getting the output") + assert.NoError(err, "failed parsing quantity") + assert.Equal(pledgeInfo.Amount, output.Quantity.ToBigInt().Uint64()) + assert.Equal(pledgeInfo.TokenType, output.Type) + assert.Equal(me, output.Owner) + + // If everything is fine, the recipient accepts and sends back her signature. + _, err = context.RunView(pledge.NewAcceptView(tx)) + assert.NoError(err, "failed to accept the claim transaction") + + // Before completing, the recipient waits for finality of the transaction + _, err = context.RunView(pledge.NewFinalityView(tx)) + assert.NoError(err, "the claim transaction was not committed") + + // Delete pledges + err = pledge.Vault(context).Delete([]*pledge.Info{pledgeInfo}) + assert.NoError(err, "failed deleting pledges") + + logger.Debugf("Respond to the issuer...done") + + return tx.ID(), nil + }, + ) +} + +// FastPledgeReClaim contains the input information for a pledge+reclaim +type FastPledgeReClaim struct { + // OriginTMSID identifies the TMS to use to perform the token operation + OriginTMSID token.TMSID + // OriginWallet is the identifier of the wallet that owns the tokens to transfer in the origin network + OriginWallet string + // Type of tokens to transfer + Type string + // Amount to transfer + Amount uint64 + // Issuer is the identity of the issuer's FSC node + Issuer view.Identity + // Recipient is the identity of the recipient's FSC node + Recipient view.Identity + // DestinationNetworkURL is the destination network's url to transfer the token to + DestinationNetworkURL string + // ReclamationDeadline is the time after which we can reclaim the funds in case they were not transferred + ReclamationDeadline time.Duration + // PledgeID is the unique identifier of the pledge + PledgeID string +} + +type FastPledgeReClaimInitiatorViewFactory struct{} + +func (f *FastPledgeReClaimInitiatorViewFactory) NewView(in []byte) (view.View, error) { + v := &FastPledgeReClaimInitiatorView{FastPledgeReClaim: &FastPledgeReClaim{}} + err := json.Unmarshal(in, v.FastPledgeReClaim) + assert.NoError(err, "failed unmarshalling input") + + return v, nil +} + +type FastPledgeReClaimInitiatorView struct { + *FastPledgeReClaim +} + +func (v *FastPledgeReClaimInitiatorView) Call(context view.Context) (interface{}, error) { + // Collect recipient's token-sdk identity + recipient, err := pledge.RequestPledgeRecipientIdentity(context, v.Recipient, v.DestinationNetworkURL, token.WithTMSID(v.OriginTMSID)) + assert.NoError(err, "failed getting recipient identity") + + // Collect issuer's token-sdk identity + issuer, err := pledge.RequestRecipientIdentity(context, v.Issuer, token.WithTMSID(v.OriginTMSID)) + assert.NoError(err, "failed getting recipient identity") + + // Create a new transaction + tx, err := pledge.NewAnonymousTransaction( + context, + ttx.WithAuditor(view3.GetIdentityProvider(context).Identity("auditor")), + ttx.WithTMSID(v.OriginTMSID), + ) + assert.NoError(err, "failed created a new transaction") + + // The sender will select tokens owned by this wallet + senderWallet := pledge.GetWallet(context, v.OriginWallet, token.WithTMSID(v.OriginTMSID)) + assert.NotNil(senderWallet, "sender wallet [%s] not found", v.OriginWallet) + + _, err = tx.Pledge(senderWallet, v.DestinationNetworkURL, v.ReclamationDeadline, recipient, issuer, v.Type, v.Amount) + assert.NoError(err, "failed pledging") + + // Collect signatures + _, err = context.RunView(pledge.NewCollectEndorsementsView(tx)) + assert.NoError(err, "failed to sign pledge transaction") + + // Last but not least, the issuer sends the transaction for ordering and waits for transaction finality. + _, err = context.RunView(pledge.NewOrderingAndFinalityView(tx)) + assert.NoError(err, "failed to commit issue transaction") + + // Inform the recipient of the pledge, + // recall that the recipient might be aware of only the other network + _, err = context.RunView(pledge.NewDistributePledgeInfoView(tx)) + assert.NoError(err, "failed to send the pledge info") + + time.Sleep(v.ReclamationDeadline) + + // Reclaim, here we are executing the reclaim protocol, therefore + // we initiate it with a fresh context + tokenID := &token2.ID{TxId: tx.ID()} + _, err = view2.Initiate(context, &ReclaimInitiatorView{ + &Reclaim{ + TMSID: v.OriginTMSID, + TokenID: tokenID, + WalletID: v.OriginWallet, + Retry: false, + }, + }) + assert.NoError(err, "failed to reclaim [%s:%s:%s]", tokenID, v.OriginTMSID, v.OriginWallet) + + return nil, nil +} + +type FastPledgeReClaimResponderView struct{} + +func (v *FastPledgeReClaimResponderView) Call(context view.Context) (interface{}, error) { + me, err := pledge.RespondRequestPledgeRecipientIdentity(context) + assert.NoError(err, "failed to respond to identity request") + + // At some point, the recipient receives the pledge info + pledgeInfo, err := pledge.ReceivePledgeInfo(context) + assert.NoError(err, "failed to receive pledge info") + + // Perform any check that is needed to validate the pledge. + logger.Debugf("The pledge info is %v", pledgeInfo) + assert.Equal(me, pledgeInfo.Script.Recipient, "recipient is different [%s]!=[%s]", me, pledgeInfo.Script.Recipient) + + // Store the pledge and send a notification back + _, err = context.RunView(pledge.NewAcceptPledgeInfoView(pledgeInfo)) + assert.NoError(err, "failed accepting pledge info") + + // Retrieve proof of existence of the passed token id + pledgeInfo, err = pledge.Vault(context).PledgeByTokenID(pledgeInfo.TokenID) + assert.NoError(err, "failed getting pledge") + assert.NotNil(pledgeInfo, "expected one pledge, got nil") + + logger.Debugf("request proof of existence") + proof, err := pledge.RequestProofOfExistence(context, pledgeInfo) + assert.NoError(err, "failed to retrieve a valid proof of existence") + assert.NotNil(proof) + + // Don't claim the token + return nil, nil +} diff --git a/integration/token/interop/views/pledge/pledge.go b/integration/token/interop/views/pledge/pledge.go new file mode 100644 index 000000000..26e302086 --- /dev/null +++ b/integration/token/interop/views/pledge/pledge.go @@ -0,0 +1,156 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + "time" + + view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/assert" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" +) + +type Result struct { + TxID string + PledgeID string +} + +// Pledge contains the input information for a transfer +type Pledge struct { + // OriginTMSID identifies the TMS to use to perform the token operation. + OriginTMSID token.TMSID + // OriginWallet is the identifier of the wallet that owns the tokens to transfer in the origin network + OriginWallet string + // Type of tokens to transfer + Type string + // Amount to transfer + Amount uint64 + // Issuer is the identity of the issuer's FSC node + Issuer view.Identity + // Recipient is the identity of the recipient's FSC node + Recipient view.Identity + // DestinationNetworkURL is the destination network's url to transfer the token to + DestinationNetworkURL string + // ReclamationDeadline is the time after which we can reclaim the funds in case they were not transferred + ReclamationDeadline time.Duration +} + +// View is the view of the initiator of a pledge operation +type View struct { + *Pledge +} + +func (v *View) Call(context view.Context) (interface{}, error) { + // Collect recipient's token-sdk identity + recipient, err := pledge.RequestPledgeRecipientIdentity(context, v.Recipient, v.DestinationNetworkURL, token.WithTMSID(v.OriginTMSID)) + assert.NoError(err, "failed getting recipient identity") + + // Collect issuer's token-sdk identity + // TODO: shall we ask for the issuer identity here and not the owner identity? + issuer, err := pledge.RequestRecipientIdentity(context, v.Issuer, token.WithTMSID(v.OriginTMSID)) + assert.NoError(err, "failed getting recipient identity") + + // Create a new transaction + tx, err := pledge.NewAnonymousTransaction( + context, + ttx.WithAuditor(view2.GetIdentityProvider(context).Identity("auditor")), + ttx.WithTMSID(v.OriginTMSID), + ) + assert.NoError(err, "failed created a new transaction") + + // The sender will select tokens owned by this wallet + senderWallet := pledge.GetWallet(context, v.OriginWallet, token.WithTMSID(v.OriginTMSID)) + assert.NotNil(senderWallet, "sender wallet [%s] not found", v.OriginWallet) + + pledgeID, err := tx.Pledge(senderWallet, v.DestinationNetworkURL, v.ReclamationDeadline, recipient, issuer, v.Type, v.Amount) + assert.NoError(err, "failed pledging") + + // Collect signatures + _, err = context.RunView(pledge.NewCollectEndorsementsView(tx)) + assert.NoError(err, "failed to sign pledge transaction") + + // Last but not least, the issuer sends the transaction for ordering and waits for transaction finality. + _, err = context.RunView(pledge.NewOrderingAndFinalityView(tx)) + assert.NoError(err, "failed to commit issue transaction") + + // Inform the recipient of the pledge, + // recall that the recipient might be aware of only the other network + _, err = context.RunView(pledge.NewDistributePledgeInfoView(tx)) + assert.NoError(err, "failed to send the pledge info") + + return json.Marshal(&Result{TxID: tx.ID(), PledgeID: pledgeID}) +} + +type ViewFactory struct{} + +func (f *ViewFactory) NewView(in []byte) (view.View, error) { + v := &View{Pledge: &Pledge{}} + err := json.Unmarshal(in, v.Pledge) + assert.NoError(err, "failed unmarshalling input") + + return v, nil +} + +type RecipientResponderView struct{} + +func (p *RecipientResponderView) Call(context view.Context) (interface{}, error) { + me, err := pledge.RespondRequestPledgeRecipientIdentity(context) + assert.NoError(err, "failed to respond to identity request") + + // At some point, the recipient receives the pledge info + pledgeInfo, err := pledge.ReceivePledgeInfo(context) + assert.NoError(err, "failed to receive pledge info") + + // Perform any check that is needed to validate the pledge. + logger.Debugf("The pledge info is %v", pledgeInfo) + assert.Equal(me, pledgeInfo.Script.Recipient, "recipient is different [%s]!=[%s]", me, pledgeInfo.Script.Recipient) + + // TODO: check pledgeInfo.Script.DestinationNetwork + + // Store the pledge and send a notification back + _, err = context.RunView(pledge.NewAcceptPledgeInfoView(pledgeInfo)) + assert.NoError(err, "failed accepting pledge info") + + return nil, nil +} + +type IssuerResponderView struct{} + +func (p *IssuerResponderView) Call(context view.Context) (interface{}, error) { + me, err := pledge.RespondRequestRecipientIdentity(context) + assert.NoError(err, "failed to respond to identity request") + + // At some point, the recipient receives the token transaction that in the meantime has been assembled + tx, err := pledge.ReceiveTransaction(context) + assert.NoError(err, "failed to receive tokens") + + outputs, err := tx.Outputs() + assert.NoError(err, "failed getting outputs") + assert.True(outputs.Count() >= 1, "expected at least one output, got [%d]", outputs.Count()) + outputs = outputs.ByScript() + assert.True(outputs.Count() == 1, "expected only one pledge output, got [%d]", outputs.Count()) + script := outputs.ScriptAt(0) + assert.NotNil(script, "expected a pledge script") + assert.Equal(me, script.Issuer, "Expected pledge script to have me (%x) as an issuer but it has %x instead", me, script.Issuer) + + // If everything is fine, the recipient accepts and sends back her signature. + // Notice that, a signature from the recipient might or might not be required to make the transaction valid. + // This depends on the driver implementation. + _, err = context.RunView(pledge.NewAcceptView(tx)) + assert.NoError(err, "failed to accept new tokens") + + // The issue is in the same Fabric network of the pledge + // Before completing, the recipient waits for finality of the transaction + _, err = context.RunView(pledge.NewFinalityView(tx)) + assert.NoError(err, "new tokens were not committed") + + return nil, nil +} diff --git a/integration/token/interop/views/pledge/reclaim.go b/integration/token/interop/views/pledge/reclaim.go new file mode 100644 index 000000000..201dfdf9a --- /dev/null +++ b/integration/token/interop/views/pledge/reclaim.go @@ -0,0 +1,114 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + "time" + + view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/assert" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/flogging" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + token2 "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" +) + +var logger = flogging.MustGetLogger("token-sdk.asset.transfer") + +// Reclaim contains the input information for a reclaim +type Reclaim struct { + TMSID token2.TMSID + // TokenID contains the identifier of the token to be reclaimed. + TokenID *token.ID + // WalletID is the identifier of the wallet that the tokens will be reclaimed to + WalletID string + // Retry tells if a retry must happen in case of a failure + Retry bool +} + +type ReclaimInitiatorView struct { + *Reclaim +} + +func (rv *ReclaimInitiatorView) Call(context view.Context) (interface{}, error) { + logger.Debugf("caller [%s]", context.Initiator()) + // Request proof of non-existence for the passed token + w, err := pledge.GetOwnerWallet(context, rv.WalletID, token2.WithTMSID(rv.TMSID)) + assert.NoError(err, "failed to retrieve wallet of owner during reclaim") + + token, script, err := pledge.Wallet(context, w).GetPledgedToken(rv.TokenID) + assert.NoError(err, "failed to retrieve token to be reclaimed") + if time.Now().Before(script.Deadline) { + return nil, errors.Errorf("cannot reclaim token yet; deadline has not elapsed yet") + } + + logger.Debugf("request proof of non-existence") + proof, err := pledge.RequestProofOfNonExistence(context, rv.TokenID, rv.TMSID, script) + if err != nil { + return nil, errors.Wrapf(err, "failed to retrieve proof of non-existence") + } + + // At this point, Alice contacts the issuer's FSC node + // to ask for the issuer's signature on the TokenID + issuerSignature, err := pledge.RequestIssuerSignature(context, rv.TokenID, rv.TMSID, script, proof) + assert.NoError(err, "failed getting issuer's signature") + assert.NotNil(issuerSignature) + + // At this point, alice is ready to prepare the token transaction. + tx, err := pledge.NewAnonymousTransaction( + context, + ttx.WithAuditor(view2.GetIdentityProvider(context).Identity("auditor")), + ttx.WithTMSID(rv.TMSID), + ) + assert.NoError(err, "failed to create a new transaction") + + // Create reclaim transaction + err = tx.Reclaim(w, token, issuerSignature, rv.TokenID, proof) + assert.NoError(err, "failed creating transaction") + + // Alice is ready to collect all the required signatures. + // Invoke the Token Chaincode to collect endorsements on the Token Request and prepare the relative Fabric transaction. + // This is all done in one shot running the following view. + // Before completing, all recipients receive the approved Fabric transaction. + // Depending on the token driver implementation, the recipient's signature might or might not be needed to make + // the token transaction valid. + _, err = context.RunView(pledge.NewCollectEndorsementsView(tx)) + assert.NoError(err, "failed to sign transaction") + + // Send to the ordering service and wait for finality + _, err = context.RunView(pledge.NewOrderingAndFinalityView(tx)) + assert.NoError(err, "failed asking ordering") + + return tx.ID(), nil +} + +type ReclaimViewFactory struct{} + +func (rvf *ReclaimViewFactory) NewView(in []byte) (view.View, error) { + rv := &ReclaimInitiatorView{Reclaim: &Reclaim{}} + err := json.Unmarshal(in, rv.Reclaim) + assert.NoError(err, "failed unmarshalling input") + return rv, nil +} + +type ReclaimIssuerResponderView struct { + WalletID string + Network string +} + +func (i *ReclaimIssuerResponderView) Call(context view.Context) (interface{}, error) { + _, err := pledge.RespondRequestIssuerSignature(context, i.WalletID) + if err != nil { + return nil, errors.Wrapf(err, "failed to respond to signature request") + } + + return nil, nil +} diff --git a/integration/token/interop/views/pledge/redeem.go b/integration/token/interop/views/pledge/redeem.go new file mode 100644 index 000000000..630d4f230 --- /dev/null +++ b/integration/token/interop/views/pledge/redeem.go @@ -0,0 +1,77 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + + view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/assert" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + token2 "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" +) + +// Redeem contains the input information for a redeem +type Redeem struct { + TMSID token2.TMSID + // TokenID contains the identifier of the token to be redeemed + TokenID *token.ID +} + +type RedeemView struct { + *Redeem +} + +func (rv *RedeemView) Call(context view.Context) (interface{}, error) { + w, err := pledge.GetIssuerWallet(context, "") + assert.NoError(err, "failed to retrieve wallet of issuer during redeem") + + wallet := pledge.NewIssuerWallet(context, w) + t, script, err := wallet.GetPledgedToken(rv.TokenID) + assert.NoError(err, "failed to retrieve pledged token during redeem") + + // make sure token was in fact claimed in the other network + proof, err := pledge.RequestProofOfExistenceOfTokenWithMetadata(context, rv.TokenID, rv.TMSID, script) + assert.NoError(err, "failed to retrieve and verify proof of token existence") + + // Create a new transaction + tx, err := pledge.NewAnonymousTransaction( + context, + ttx.WithAuditor(view2.GetIdentityProvider(context).Identity("auditor")), + ttx.WithTMSID(rv.TMSID), + ) + assert.NoError(err, "failed created a new transaction") + + ow, err := pledge.GetOwnerWallet(context, "") + assert.NoError(err, "failed to retrieve owner wallet") + + err = tx.RedeemPledge(ow, t, rv.TokenID, proof) + assert.NoError(err, "failed adding redeem") + + // Collect signatures + _, err = context.RunView(pledge.NewCollectEndorsementsView(tx)) + assert.NoError(err, "failed to sign redeem transaction") + + // Sends the transaction for ordering and wait for transaction finality + _, err = context.RunView(pledge.NewOrderingAndFinalityView(tx)) + assert.NoError(err, "failed to commit redeem transaction") + + return tx.ID(), nil +} + +type RedeemViewFactory struct{} + +func (rvf *RedeemViewFactory) NewView(in []byte) (view.View, error) { + v := &RedeemView{Redeem: &Redeem{}} + err := json.Unmarshal(in, v.Redeem) + assert.NoError(err, "failed unmarshalling input") + + return v, nil +} diff --git a/integration/token/interop/views/pledge/scan.go b/integration/token/interop/views/pledge/scan.go new file mode 100644 index 000000000..df31edf82 --- /dev/null +++ b/integration/token/interop/views/pledge/scan.go @@ -0,0 +1,56 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + "time" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/assert" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" +) + +// Scan contains the input information for a scan of a matching pledge id +type Scan struct { + // TMSID identifies the TMS to use to perform the token operation + TMSID token.TMSID + // PlegdeID is the identifier to use in the scan + PledgeID string + // Timeout of the scan + Timeout time.Duration + // StartingTransactionID is the transaction id from which to start the scan. + // If empty, the scan starts from the genesis block + StartingTransactionID string +} + +type ScanView struct { + *Scan +} + +func (s *ScanView) Call(context view.Context) (interface{}, error) { + b, err := pledge.IDExists( + context, + s.PledgeID, + s.Timeout, + token.WithTMSID(s.TMSID), + pledge.WithStartingTransaction(s.StartingTransactionID), + ) + assert.NoError(err, "failed to scan for pledge id") + return b, nil +} + +type ScanViewFactory struct{} + +func (s *ScanViewFactory) NewView(in []byte) (view.View, error) { + f := &ScanView{Scan: &Scan{}} + err := json.Unmarshal(in, f.Scan) + assert.NoError(err, "failed unmarshalling input") + + return f, nil +} diff --git a/interop.mk b/interop.mk index 18c533411..2c8fe844f 100644 --- a/interop.mk +++ b/interop.mk @@ -22,6 +22,10 @@ integration-tests-interop-dlog-t5: integration-tests-interop-dlog-t6: make integration-tests-interop-dlog TEST_FILTER="T6" +.PHONY: integration-tests-interop-dlog-t7 +integration-tests-interop-dlog-t7: + make integration-tests-interop-dlog TEST_FILTER="T7" + .PHONY: integration-tests-interop-dlog integration-tests-interop-dlog: cd ./integration/token/interop/dlog; export FAB_BINS=$(FAB_BINS); ginkgo $(GINKGO_TEST_OPTS) --label-filter="$(TEST_FILTER)" . @@ -54,3 +58,7 @@ integration-tests-interop-fabtoken-t6: .PHONY: integration-tests-interop-fabtoken integration-tests-interop-fabtoken: cd ./integration/token/interop/fabtoken; export FAB_BINS=$(FAB_BINS); ginkgo $(GINKGO_TEST_OPTS) --label-filter="$(TEST_FILTER)" . + +.PHONY: integration-tests-interop-fabtoken-t7 +integration-tests-interop-fabtoken-t7: + cd ./integration/token/interop/fabtoken; export FAB_BINS=$(FAB_BINS); ginkgo $(GINKGO_TEST_OPTS) --focus "Asset Transfer With Two Fabric Networks" . diff --git a/token/core/common/interop/htlc/validator_htlc.go b/token/core/common/interop/htlc/validator_htlc.go new file mode 100644 index 000000000..b067a304b --- /dev/null +++ b/token/core/common/interop/htlc/validator_htlc.go @@ -0,0 +1,88 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package htlc + +import ( + "encoding/json" + "time" + + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" + "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" + "github.com/pkg/errors" +) + +// TransferHTLCValidate checks the validity of the HTLC scripts, if any +func TransferHTLCValidate[P driver.PublicParameters, T driver.Output, TA driver.TransferAction, IA driver.IssueAction, DS driver.Deserializer](ctx *common.Context[P, T, TA, IA, DS]) error { + now := time.Now() + + for i, in := range ctx.InputTokens { + owner, err := identity.UnmarshalTypedIdentity(in.GetOwner()) + if err != nil { + return errors.Wrap(err, "failed to unmarshal owner of input token") + } + // is it owned by an htlc script? + if owner.Type == htlc.ScriptType { + // Then, the first output must be compatible with this input. + if len(ctx.TransferAction.GetOutputs()) != 1 { + return errors.New("invalid transfer action: an htlc script only transfers the ownership of a token") + } + + // check it is not a redeem + output := ctx.TransferAction.GetOutputs()[0] + if output.IsRedeem() { + return errors.New("invalid transfer action: the output corresponding to an htlc spending should not be a redeem") + } + + // check that owner field in output is correct + script, op, err := htlc.VerifyOwner(ctx.InputTokens[0].GetOwner(), output.GetOwner(), now) + if err != nil { + return errors.Wrap(err, "failed to verify transfer from htlc script") + } + + // check metadata + sigma := ctx.Signatures[i] + metadataKey, err := htlc.MetadataClaimKeyCheck(ctx.TransferAction, script, op, sigma) + if err != nil { + return errors.WithMessagef(err, "failed to check htlc metadata") + } + if op != htlc.Reclaim { + ctx.CountMetadataKey(metadataKey) + } + } + } + + for _, out := range ctx.TransferAction.GetOutputs() { + if out.IsRedeem() { + continue + } + + // if it is an htlc script then the deadline must still be valid + owner, err := identity.UnmarshalTypedIdentity(out.GetOwner()) + if err != nil { + return err + } + if owner.Type == htlc.ScriptType { + script := &htlc.Script{} + err = json.Unmarshal(owner.Identity, script) + if err != nil { + return err + } + if err := script.Validate(now); err != nil { + return errors.WithMessagef(err, "htlc script invalid") + } + metadataKey, err := htlc.MetadataLockKeyCheck(ctx.TransferAction, script) + if err != nil { + return errors.WithMessagef(err, "failed to check htlc metadata") + } + ctx.CountMetadataKey(metadataKey) + continue + } + } + return nil +} diff --git a/token/core/common/interop/pledge/metadata.go b/token/core/common/interop/pledge/metadata.go new file mode 100644 index 000000000..6e0db26ff --- /dev/null +++ b/token/core/common/interop/pledge/metadata.go @@ -0,0 +1,62 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" + "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" +) + +const ( + TokenIDKey = pledge.TokenIDKey + NetworkKey = pledge.NetworkKey + ProofKey = pledge.ProofKey + ProofOfClaimSuffix = "proof_of_claim" +) + +type IssueMetadata struct { + // OriginTokenID is the identifier of the pledged token in the origin network + OriginTokenID *token.ID + // OriginNetwork is the network where the pledge took place + OriginNetwork string +} + +func IssueActionMetadata(attributes map[string][]byte, opts *driver.IssueOptions) (map[string][]byte, error) { + if len(opts.Attributes) == 0 { + return attributes, nil + } + + tokenID, hasTokenID := opts.Attributes[TokenIDKey] + network, hasNetwork := opts.Attributes[NetworkKey] + if !hasTokenID && !hasNetwork { + return attributes, nil + } + + if hasTokenID && hasNetwork { + marshalled, err := json.Marshal(&IssueMetadata{tokenID.(*token.ID), network.(string)}) + if err != nil { + return nil, err + } + key := common.Hashable(marshalled).String() + attributes[key] = marshalled + + // append proof, if needed + var proof []byte + proofOpt, hasProof := opts.Attributes[ProofKey] + if hasProof { + proof = proofOpt.([]byte) + } + attributes[key+ProofOfClaimSuffix] = proof + return attributes, nil + } + + return attributes, nil +} diff --git a/token/core/common/interop/pledge/metadata_test.go b/token/core/common/interop/pledge/metadata_test.go new file mode 100644 index 000000000..a7fbc7e6d --- /dev/null +++ b/token/core/common/interop/pledge/metadata_test.go @@ -0,0 +1,148 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + "testing" + + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" + "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/stretchr/testify/assert" +) + +func TestIssueActionMetadata(t *testing.T) { + tests := []struct { + name string + attributes map[string][]byte + opts *driver.IssueOptions + expected map[string][]byte + errExpected bool + }{ + { + name: "empty attributes", + attributes: map[string][]byte{}, + opts: &driver.IssueOptions{ + Attributes: map[interface{}]interface{}{}, + }, + expected: map[string][]byte{}, + errExpected: false, + }, + { + name: "missing tokenID", + attributes: map[string][]byte{ + NetworkKey: []byte("network"), + }, + opts: &driver.IssueOptions{ + Attributes: map[interface{}]interface{}{ + NetworkKey: "network", + }, + }, + expected: map[string][]byte{ + NetworkKey: []byte("network"), + }, + errExpected: false, + }, + { + name: "missing network", + attributes: map[string][]byte{ + TokenIDKey: []byte("tokenID"), + }, + opts: &driver.IssueOptions{ + Attributes: map[interface{}]interface{}{ + TokenIDKey: "tokenID", + }, + }, + expected: map[string][]byte{ + TokenIDKey: []byte("tokenID"), + }, + errExpected: false, + }, + { + name: "valid attributes", + attributes: map[string][]byte{ + NetworkKey: []byte("network"), + TokenIDKey: []byte("tokenID"), + }, + opts: &driver.IssueOptions{ + Attributes: map[interface{}]interface{}{ + NetworkKey: "network", + TokenIDKey: &token.ID{ + TxId: "a_transaction", + Index: 2, + }, + }, + }, + expected: func() map[string][]byte { + metadata := &IssueMetadata{ + OriginTokenID: &token.ID{ + TxId: "a_transaction", + Index: 2, + }, + OriginNetwork: "network", + } + marshalled, err := json.Marshal(metadata) + assert.NoError(t, err) + key := common.Hashable(marshalled).String() + res := map[string][]byte{ + NetworkKey: []byte("network"), + TokenIDKey: []byte("tokenID"), + key: marshalled, + key + ProofOfClaimSuffix: nil, + } + return res + }(), + errExpected: false, + }, + { + name: "valid attributes with proof", + attributes: map[string][]byte{}, + opts: &driver.IssueOptions{ + Attributes: map[interface{}]interface{}{ + NetworkKey: "network", + TokenIDKey: &token.ID{ + TxId: "a_transaction", + Index: 2, + }, + ProofKey: []byte("proof"), + }, + }, + expected: func() map[string][]byte { + metadata := &IssueMetadata{ + OriginTokenID: &token.ID{ + TxId: "a_transaction", + Index: 2, + }, + OriginNetwork: "network", + } + marshalled, err := json.Marshal(metadata) + assert.NoError(t, err) + key := common.Hashable(marshalled).String() + res := map[string][]byte{ + key: marshalled, + key + "proof_of_claim": []byte("proof"), + } + return res + }(), + errExpected: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result, err := IssueActionMetadata(test.attributes, test.opts) + if test.errExpected && err == nil { + t.Errorf("expected error but got none") + } + if !test.errExpected && err != nil { + t.Errorf("got error %v", err) + } + assert.Equal(t, test.expected, result) + }) + } +} diff --git a/token/core/common/interop/pledge/validator_pledge.go b/token/core/common/interop/pledge/validator_pledge.go new file mode 100644 index 000000000..afbe3832d --- /dev/null +++ b/token/core/common/interop/pledge/validator_pledge.go @@ -0,0 +1,115 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "bytes" + "encoding/json" + "fmt" + "time" + + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" + "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" + "github.com/pkg/errors" +) + +func IssuePledgeValidate[P driver.PublicParameters, T driver.Output, TA driver.TransferAction, IA driver.IssueAction, DS driver.Deserializer](ctx *common.Context[P, T, TA, IA, DS]) error { + for k := range ctx.IssueAction.GetMetadata() { + ctx.CountMetadataKey(k) + } + return nil +} + +func TransferPledgeValidate[P driver.PublicParameters, T driver.Output, TA driver.TransferAction, IA driver.IssueAction, DS driver.Deserializer](ctx *common.Context[P, T, TA, IA, DS]) error { + for _, in := range ctx.InputTokens { + id, err := identity.UnmarshalTypedIdentity(in.GetOwner()) + if err != nil { + return errors.Wrap(err, "failed to unmarshal owner of input token") + } + if id.Type == pledge.ScriptType { + if len(ctx.InputTokens) != 1 || len(ctx.TransferAction.GetOutputs()) != 1 { + return errors.Errorf("invalid transfer action: a pledge script only transfers the ownership of a token") + } + out := ctx.TransferAction.GetOutputs()[0] + sender, err := identity.UnmarshalTypedIdentity(ctx.InputTokens[0].GetOwner()) + if err != nil { + return err + } + script := &pledge.Script{} + err = json.Unmarshal(sender.Identity, script) + if err != nil { + return err + } + if time.Now().Before(script.Deadline) { + return errors.New("cannot reclaim pledge yet: wait for timeout to elapse.") + } + + key, err := constructMetadataKey(ctx.TransferAction) + if err != nil { + return errors.Wrap(err, "failed constructing metadata key") + } + + var metadataKey string + if out.IsRedeem() { + metadataKey = pledge.RedeemPledgeKey + key + } else { + metadataKey = pledge.MetadataReclaimKey + key + if !script.Sender.Equal(out.GetOwner()) { + return errors.New("recipient of token does not correspond to sender of reclaim request") + } + } + + v, ok := ctx.TransferAction.GetMetadata()[metadataKey] + if !ok { + return errors.Errorf("metadata key not found [%s]", metadataKey) + } + if v == nil { + return errors.Errorf("empty metadata for key [%s]", metadataKey) + } + ctx.CountMetadataKey(metadataKey) + } + } + + for _, out := range ctx.TransferAction.GetOutputs() { + if out.IsRedeem() { + continue + } + owner, err := identity.UnmarshalTypedIdentity(out.GetOwner()) + if err != nil { + return err + } + if owner.Type == pledge.ScriptType { + script := &pledge.Script{} + err = json.Unmarshal(owner.Identity, script) + if err != nil { + return err + } + if script.Deadline.Before(time.Now()) { + return errors.Errorf("pledge script is invalid: expiration date has already passed") + } + v, ok := ctx.TransferAction.GetMetadata()[pledge.MetadataKey+script.ID] + if !ok { + return errors.Errorf("empty metadata for pledge script with identifier %s", script.ID) + } + if !bytes.Equal(v, []byte("1")) { + return errors.Errorf("invalid metadatata for pledge script with identifier %s", script.ID) + } + ctx.CountMetadataKey(pledge.MetadataKey + script.ID) + } + } + return nil +} + +func constructMetadataKey(action driver.TransferAction) (string, error) { + inputs := action.GetInputs() + if len(inputs) != 1 { + return "", errors.New("invalid transfer action, does not carry a single input") + } + return fmt.Sprintf(".%d.%s", inputs[0].Index, inputs[0].TxId), nil +} diff --git a/token/core/common/logging/logger.go b/token/core/common/logging/logger.go index 5bb3d6768..d8eb007c3 100644 --- a/token/core/common/logging/logger.go +++ b/token/core/common/logging/logger.go @@ -18,6 +18,10 @@ const loggerNameSeparator = "." // Logger provides logging API type Logger = logging.Logger +func MustGetLogger(name string) Logger { + return logging.MustGetLogger(name) +} + func DriverLogger(prefix string, networkID string, channel string, namespace string) Logger { return logging.MustGetLogger(loggerName(prefix, networkID, channel, namespace)) } diff --git a/token/core/fabtoken/actions.go b/token/core/fabtoken/actions.go index a08ed086d..cc604f83f 100644 --- a/token/core/fabtoken/actions.go +++ b/token/core/fabtoken/actions.go @@ -31,19 +31,23 @@ func (m *OutputMetadata) Serialize() ([]byte, error) { } // Output carries the output of an action -type Output struct { - Output *token.Token -} +type Output token.Token // Serialize marshals a Output func (t *Output) Serialize() ([]byte, error) { - return json.Marshal(t.Output) + return json.Marshal(t) } -// IsRedeem returns true if the owner of a Output is empty -// todo update interface to account for nil t.Output.Owner and nil t.Output +// IsRedeem returns true if the owner of a Owner is empty func (t *Output) IsRedeem() bool { - return len(t.Output.Owner.Raw) == 0 + return t.Owner == nil || len(t.Owner.Raw) == 0 +} + +func (t *Output) GetOwner() []byte { + if t.Owner != nil { + return t.Owner.Raw + } + return nil } // IssueAction encodes a fabtoken Issue @@ -122,7 +126,7 @@ type TransferAction struct { // identifier of token to be transferred Inputs []*token.ID // InputTokens are the inputs transferred by this action - InputTokens []*token.Token + InputTokens []*Output // outputs to be created as a result of the transfer Outputs []*Output // Metadata contains the transfer action's metadata diff --git a/token/core/fabtoken/driver/deserializer.go b/token/core/fabtoken/driver/deserializer.go index 0b089df9a..790cc3755 100644 --- a/token/core/fabtoken/driver/deserializer.go +++ b/token/core/fabtoken/driver/deserializer.go @@ -10,10 +10,10 @@ import ( "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" "github.com/hyperledger-labs/fabric-token-sdk/token/core/fabtoken" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/deserializer" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/interop/htlc" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/x509" htlc2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" ) // Deserializer deserializes verifiers associated with issuers, owners, and auditors @@ -25,7 +25,8 @@ type Deserializer struct { func NewDeserializer() *Deserializer { m := deserializer.NewTypedVerifierDeserializerMultiplex(&x509.AuditMatcherDeserializer{}) m.AddTypedVerifierDeserializer(msp.X509Identity, deserializer.NewTypedIdentityVerifierDeserializer(&x509.MSPIdentityDeserializer{})) - m.AddTypedVerifierDeserializer(htlc2.ScriptType, htlc.NewTypedIdentityDeserializer(m)) + m.AddTypedVerifierDeserializer(htlc2.ScriptType, htlc2.NewTypedIdentityDeserializer(m)) + m.AddTypedVerifierDeserializer(pledge.ScriptType, pledge.NewTypedIdentityDeserializer(m)) return &Deserializer{ Deserializer: common.NewDeserializer( @@ -52,6 +53,7 @@ type EIDRHDeserializer = deserializer.EIDRHDeserializer func NewEIDRHDeserializer() *EIDRHDeserializer { d := deserializer.NewEIDRHDeserializer() d.AddDeserializer(msp.X509Identity, &x509.AuditInfoDeserializer{}) - d.AddDeserializer(htlc2.ScriptType, htlc.NewAuditDeserializer(&x509.AuditInfoDeserializer{})) + d.AddDeserializer(htlc2.ScriptType, htlc2.NewAuditDeserializer(&x509.AuditInfoDeserializer{})) + d.AddDeserializer(pledge.ScriptType, pledge.NewAuditDeserializer(&x509.AuditInfoDeserializer{})) return d } diff --git a/token/core/fabtoken/driver/driver.go b/token/core/fabtoken/driver/driver.go index e79bac8bf..298f553aa 100644 --- a/token/core/fabtoken/driver/driver.go +++ b/token/core/fabtoken/driver/driver.go @@ -12,14 +12,17 @@ import ( view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/server/view" tracing2 "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/tracing" "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/interop/pledge" "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/logging" "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/metrics" "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/observables" "github.com/hyperledger-labs/fabric-token-sdk/token/core/fabtoken" + _ "github.com/hyperledger-labs/fabric-token-sdk/token/core/fabtoken/driver/interop/state/fabric" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" "github.com/hyperledger-labs/fabric-token-sdk/token/services/config" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" + pledge2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" "github.com/hyperledger-labs/fabric-token-sdk/token/services/network" "github.com/pkg/errors" "go.opentelemetry.io/otel/trace" @@ -109,6 +112,7 @@ func (d *Driver) NewTokenService(_ driver.ServiceProvider, networkID string, cha authorization := common.NewAuthorizationMultiplexer( common.NewTMSAuthorization(logger, publicParamsManager.PublicParams(), ws), htlc.NewScriptAuth(ws), + pledge2.NewScriptAuth(ws), ) service, err := fabtoken.NewService( logger, @@ -119,7 +123,14 @@ func (d *Driver) NewTokenService(_ driver.ServiceProvider, networkID string, cha deserializer, tmsConfig, observables.NewObservableIssueService( - fabtoken.NewIssueService(publicParamsManager, ws, deserializer), + fabtoken.NewIssueService( + publicParamsManager, + ws, + deserializer, + []fabtoken.IssueMetadataProviderFunc{ + pledge.IssueActionMetadata, + }, + ), observables.NewIssue(tracerProvider), ), observables.NewObservableTransferService( diff --git a/token/core/fabtoken/driver/interop/state/fabric/state.go b/token/core/fabtoken/driver/interop/state/fabric/state.go new file mode 100644 index 000000000..45437fd9b --- /dev/null +++ b/token/core/fabtoken/driver/interop/state/fabric/state.go @@ -0,0 +1,91 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fabric + +import ( + "encoding/json" + + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/logging" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/fabtoken" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" + fabric3 "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state/fabric" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state/fabric/core" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/network/common/rws/keys" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" + "go.uber.org/dig" +) + +type Validator struct{} + +func (v *Validator) Validate(tokRaw []byte, info *pledge.Info) error { + tok := &token.Token{} + err := json.Unmarshal(tokRaw, tok) + if err != nil { + return errors.Wrapf(err, "failed to unmarshal token [%s]", common.Hashable(tokRaw)) + } + + if tok.Type != info.TokenType { + return errors.Errorf("type of pledge token does not match type in claim request") + } + q, err := token.ToQuantity(tok.Quantity, 64) + if err != nil { + return errors.Wrapf(err, "failed converting token quantity [%s]", tok.Quantity) + } + expectedQ := token.NewQuantityFromUInt64(info.Amount) + if expectedQ.Cmp(q) != 0 { + return errors.Errorf("quantity in pledged token is different from quantity in claim request") + } + owner, err := identity.UnmarshalTypedIdentity(tok.Owner.Raw) + if err != nil { + return errors.Wrapf(err, "failed to unmarshal owner of token [%s]", common.Hashable(tokRaw)) + } + if owner.Type != pledge.ScriptType { + return err + } + script := &pledge.Script{} + err = json.Unmarshal(owner.Identity, script) + if err != nil { + return errors.Wrapf(err, "failed to unmarshal pledge script [%s]", common.Hashable(tokRaw)) + } + if script.Recipient == nil { + return errors.Errorf("script in proof encodes invalid recipient") + } + if !script.Recipient.Equal(info.Script.Recipient) { + return errors.Errorf("recipient in claim request does not match recipient in proof") + } + if script.Deadline != info.Script.Deadline { + return errors.Errorf("deadline in claim request does not match deadline in proof") + } + if script.DestinationNetwork != info.Script.DestinationNetwork { + return errors.Errorf("destination network in claim request does not match destination network in proof [%s vs.%s]", info.Script.DestinationNetwork, script.DestinationNetwork) + } + + return nil +} + +func NewStateDriver(in struct { + dig.In + FNSProvider *fabric.NetworkServiceProvider + RelayProvider fabric3.RelayProvider + VaultStore *pledge.VaultStore +}) fabric3.NamedStateDriver { + return fabric3.NamedStateDriver{ + Name: fabtoken.PublicParameters, + Driver: core.NewStateDriver( + logging.MustGetLogger("token-sdk.core.fabtoken"), + in.FNSProvider, + in.RelayProvider, + in.VaultStore, + &Validator{}, + &keys.Translator{}, + ), + } +} diff --git a/token/core/fabtoken/issue.go b/token/core/fabtoken/issue.go index 7b655d060..749b03121 100644 --- a/token/core/fabtoken/issue.go +++ b/token/core/fabtoken/issue.go @@ -14,14 +14,27 @@ import ( "github.com/pkg/errors" ) +type IssueMetadataProviderFunc = func(attributes map[string][]byte, opts *driver.IssueOptions) (map[string][]byte, error) + type IssueService struct { - PublicParamsManager driver.PublicParamsManager - WalletService driver.WalletService - Deserializer driver.Deserializer + PublicParamsManager driver.PublicParamsManager + WalletService driver.WalletService + Deserializer driver.Deserializer + IssueMetadataProviders []IssueMetadataProviderFunc } -func NewIssueService(publicParamsManager driver.PublicParamsManager, walletService driver.WalletService, deserializer driver.Deserializer) *IssueService { - return &IssueService{PublicParamsManager: publicParamsManager, WalletService: walletService, Deserializer: deserializer} +func NewIssueService( + publicParamsManager driver.PublicParamsManager, + walletService driver.WalletService, + deserializer driver.Deserializer, + IssueMetadataProviders []IssueMetadataProviderFunc, +) *IssueService { + return &IssueService{ + PublicParamsManager: publicParamsManager, + WalletService: walletService, + Deserializer: deserializer, + IssueMetadataProviders: IssueMetadataProviders, + } } // Issue returns an IssueAction as a function of the passed arguments @@ -48,13 +61,11 @@ func (s *IssueService) Issue(ctx context.Context, issuerIdentity driver.Identity return nil, nil, errors.Wrapf(err, "failed to convert [%d] to quantity of precision [%d]", v, precision) } outs = append(outs, &Output{ - Output: &token2.Token{ - Owner: &token2.Owner{ - Raw: owners[i], - }, - Type: tokenType, - Quantity: q.Hex(), + Owner: &token2.Owner{ + Raw: owners[i], }, + Type: tokenType, + Quantity: q.Hex(), }) meta := &OutputMetadata{ @@ -67,7 +78,20 @@ func (s *IssueService) Issue(ctx context.Context, issuerIdentity driver.Identity outputsMetadata = append(outputsMetadata, metaRaw) } - action := &IssueAction{Issuer: issuerIdentity, Outputs: outs} + actionMetadata := map[string][]byte{} + var err error + for _, provider := range s.IssueMetadataProviders { + actionMetadata, err = provider(actionMetadata, opts) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed generating token action metadata") + } + } + + action := &IssueAction{ + Issuer: issuerIdentity, + Outputs: outs, + Metadata: actionMetadata, + } outputs, err := action.GetSerializedOutputs() if err != nil { return nil, nil, err diff --git a/token/core/fabtoken/transfer.go b/token/core/fabtoken/transfer.go index 6068fe815..07a1aaec8 100644 --- a/token/core/fabtoken/transfer.go +++ b/token/core/fabtoken/transfer.go @@ -43,7 +43,14 @@ func NewTransferService( // Transfer returns a TransferAction as a function of the passed arguments // It also returns the corresponding TransferMetadata -func (s *TransferService) Transfer(ctx context.Context, txID string, wallet driver.OwnerWallet, tokenIDs []*token.ID, Outputs []*token.Token, opts *driver.TransferOptions) (driver.TransferAction, *driver.TransferMetadata, error) { +func (s *TransferService) Transfer( + ctx context.Context, + txID string, + wallet driver.OwnerWallet, + tokenIDs []*token.ID, + outputs []*token.Token, + opts *driver.TransferOptions, +) (driver.TransferAction, *driver.TransferMetadata, error) { // select inputs inputTokens, err := s.TokenLoader.GetTokens(tokenIDs) if err != nil { @@ -51,17 +58,25 @@ func (s *TransferService) Transfer(ctx context.Context, txID string, wallet driv } var senders []driver.Identity + var iTokens []*Output for _, tok := range inputTokens { s.Logger.Debugf("Selected output [%s,%s,%s]", tok.Type, tok.Quantity, driver.Identity(tok.Owner.Raw)) senders = append(senders, tok.Owner.Raw) + iTokens = append(iTokens, &Output{ + Type: tok.Type, + Quantity: tok.Quantity, + Owner: tok.Owner, + }) } // prepare outputs var outs []*Output var outputsMetadata [][]byte - for _, output := range Outputs { + for _, output := range outputs { outs = append(outs, &Output{ - Output: output, + Owner: output.Owner, + Type: output.Type, + Quantity: output.Quantity, }) meta := &OutputMetadata{} metaRaw, err := meta.Serialize() @@ -74,7 +89,7 @@ func (s *TransferService) Transfer(ctx context.Context, txID string, wallet driv // assemble transfer action transfer := &TransferAction{ Inputs: tokenIDs, - InputTokens: inputTokens, + InputTokens: iTokens, Outputs: outs, Metadata: meta.TransferActionMetadata(opts.Attributes), } @@ -85,22 +100,22 @@ func (s *TransferService) Transfer(ctx context.Context, txID string, wallet driv var receivers []driver.Identity var outputAuditInfos [][]byte for i, output := range outs { - if output.Output == nil || output.Output.Owner == nil { + if output == nil || output.Owner == nil { return nil, nil, errors.Errorf("failed to transfer: invalid output at index %d", i) } - if len(output.Output.Owner.Raw) == 0 { // redeem - receivers = append(receivers, output.Output.Owner.Raw) + if len(output.Owner.Raw) == 0 { // redeem + receivers = append(receivers, output.Owner.Raw) outputAuditInfos = append(outputAuditInfos, []byte{}) continue } - recipients, err := s.Deserializer.Recipients(output.Output.Owner.Raw) + recipients, err := s.Deserializer.Recipients(output.Owner.Raw) if err != nil { return nil, nil, errors.Wrap(err, "failed getting recipients") } receivers = append(receivers, recipients...) - auditInfo, err := s.Deserializer.GetOwnerAuditInfo(output.Output.Owner.Raw, ws) + auditInfo, err := s.Deserializer.GetOwnerAuditInfo(output.Owner.Raw, ws) if err != nil { - return nil, nil, errors.Wrapf(err, "failed getting audit info for sender identity [%s]", driver.Identity(output.Output.Owner.Raw).String()) + return nil, nil, errors.Wrapf(err, "failed getting audit info for sender identity [%s]", driver.Identity(output.Owner.Raw).String()) } outputAuditInfos = append(outputAuditInfos, auditInfo...) } @@ -124,9 +139,10 @@ func (s *TransferService) Transfer(ctx context.Context, txID string, wallet driv if err != nil { return nil, nil, errors.Wrapf(err, "failed getting audit info for recipient identity [%s]", receiver.String()) } + s.Logger.Debugf("receiver audit info for [%s] is [%s]", receiver, auditInfo) receiverAuditInfos = append(receiverAuditInfos, auditInfo...) } - outputs, err := transfer.GetSerializedOutputs() + serializedOutputs, err := transfer.GetSerializedOutputs() if err != nil { return nil, nil, errors.Wrapf(err, "failed getting serialized outputs") } @@ -141,7 +157,7 @@ func (s *TransferService) Transfer(ctx context.Context, txID string, wallet driv TokenIDs: tokenIDs, Senders: senders, SenderAuditInfos: senderAuditInfos, - Outputs: outputs, + Outputs: serializedOutputs, OutputsMetadata: outputsMetadata, OutputAuditInfos: outputAuditInfos, Receivers: receivers, diff --git a/token/core/fabtoken/validator.go b/token/core/fabtoken/validator.go index 8eb537e10..e439d9739 100644 --- a/token/core/fabtoken/validator.go +++ b/token/core/fabtoken/validator.go @@ -8,14 +8,15 @@ package fabtoken import ( "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/interop/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/interop/pledge" "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/logging" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" - "github.com/hyperledger-labs/fabric-token-sdk/token/token" ) -type ValidateTransferFunc = common.ValidateTransferFunc[*PublicParams, *token.Token, *TransferAction, *IssueAction, driver.Deserializer] +type ValidateTransferFunc = common.ValidateTransferFunc[*PublicParams, *Output, *TransferAction, *IssueAction, driver.Deserializer] -type ValidateIssueFunc = common.ValidateIssueFunc[*PublicParams, *token.Token, *TransferAction, *IssueAction, driver.Deserializer] +type ValidateIssueFunc = common.ValidateIssueFunc[*PublicParams, *Output, *TransferAction, *IssueAction, driver.Deserializer] type ActionDeserializer struct{} @@ -41,23 +42,25 @@ func (a *ActionDeserializer) DeserializeActions(tr *driver.TokenRequest) ([]*Iss return issueActions, transferActions, nil } -type Context = common.Context[*PublicParams, *token.Token, *TransferAction, *IssueAction, driver.Deserializer] +type Context = common.Context[*PublicParams, *Output, *TransferAction, *IssueAction, driver.Deserializer] -type Validator = common.Validator[*PublicParams, *token.Token, *TransferAction, *IssueAction, driver.Deserializer] +type Validator = common.Validator[*PublicParams, *Output, *TransferAction, *IssueAction, driver.Deserializer] func NewValidator(logger logging.Logger, pp *PublicParams, deserializer driver.Deserializer, extraValidators ...ValidateTransferFunc) *Validator { transferValidators := []ValidateTransferFunc{ TransferSignatureValidate, TransferBalanceValidate, - TransferHTLCValidate, + htlc.TransferHTLCValidate[*PublicParams, *Output, *TransferAction, *IssueAction, driver.Deserializer], + pledge.TransferPledgeValidate[*PublicParams, *Output, *TransferAction, *IssueAction, driver.Deserializer], } transferValidators = append(transferValidators, extraValidators...) issueValidators := []ValidateIssueFunc{ IssueValidate, + pledge.IssuePledgeValidate[*PublicParams, *Output, *TransferAction, *IssueAction, driver.Deserializer], } - return common.NewValidator[*PublicParams, *token.Token, *TransferAction, *IssueAction, driver.Deserializer]( + return common.NewValidator[*PublicParams, *Output, *TransferAction, *IssueAction, driver.Deserializer]( logger, pp, deserializer, diff --git a/token/core/fabtoken/validator_issue.go b/token/core/fabtoken/validator_issue.go index f727bda46..141a67c61 100644 --- a/token/core/fabtoken/validator_issue.go +++ b/token/core/fabtoken/validator_issue.go @@ -21,7 +21,7 @@ func IssueValidate(ctx *Context) error { return errors.Errorf("there is no output") } for _, output := range action.GetOutputs() { - out := output.(*Output).Output + out := output.(*Output) q, err := token.ToQuantity(out.Quantity, ctx.PP.QuantityPrecision) if err != nil { return errors.Wrapf(err, "failed parsing quantity [%s]", out.Quantity) diff --git a/token/core/fabtoken/validator_transfer.go b/token/core/fabtoken/validator_transfer.go index 411a46fa2..a4192db7c 100644 --- a/token/core/fabtoken/validator_transfer.go +++ b/token/core/fabtoken/validator_transfer.go @@ -7,19 +7,17 @@ SPDX-License-Identifier: Apache-2.0 package fabtoken import ( - "encoding/json" - "time" - "github.com/hyperledger-labs/fabric-token-sdk/token/driver" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" - htlc2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/interop/htlc" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" "github.com/hyperledger-labs/fabric-token-sdk/token/token" "github.com/pkg/errors" ) // TransferSignatureValidate validates the signatures for the inputs spent by an action func TransferSignatureValidate(ctx *Context) error { + if len(ctx.TransferAction.Inputs) != len(ctx.TransferAction.InputTokens) { + return errors.Errorf("invalid number of token inputs") + } + ctx.InputTokens = ctx.TransferAction.InputTokens for _, tok := range ctx.InputTokens { ctx.Logger.Debugf("check sender [%s]", driver.Identity(tok.Owner.Raw).UniqueID()) @@ -34,6 +32,7 @@ func TransferSignatureValidate(ctx *Context) error { } ctx.Signatures = append(ctx.Signatures, sigma) } + return nil } @@ -66,7 +65,7 @@ func TransferBalanceValidate(ctx *Context) error { } } for _, output := range ctx.TransferAction.GetOutputs() { - out := output.(*Output).Output + out := output.(*Output) q, err := token.ToQuantity(out.Quantity, ctx.PP.QuantityPrecision) if err != nil { return errors.Wrapf(err, "failed parsing quantity [%s]", out.Quantity) @@ -84,84 +83,3 @@ func TransferBalanceValidate(ctx *Context) error { return nil } - -// TransferHTLCValidate checks the validity of the HTLC scripts, if any -func TransferHTLCValidate(ctx *Context) error { - now := time.Now() - - for i, in := range ctx.InputTokens { - owner, err := identity.UnmarshalTypedIdentity(in.Owner.Raw) - if err != nil { - return errors.Wrap(err, "failed to unmarshal owner of input token") - } - // is it owned by an htlc script? - if owner.Type == htlc.ScriptType { - // Then, the first output must be compatible with this input. - if len(ctx.TransferAction.GetOutputs()) != 1 { - return errors.New("invalid transfer action: an htlc script only transfers the ownership of a token") - } - - // check type and quantity - output := ctx.TransferAction.GetOutputs()[0].(*Output) - tok := output.Output - if ctx.InputTokens[0].Type != tok.Type { - return errors.New("invalid transfer action: type of input does not match type of output") - } - if ctx.InputTokens[0].Quantity != tok.Quantity { - return errors.New("invalid transfer action: quantity of input does not match quantity of output") - } - if output.IsRedeem() { - return errors.New("invalid transfer action: the output corresponding to an htlc spending should not be a redeem") - } - - // check owner field - script, op, err := htlc2.VerifyOwner(ctx.InputTokens[0].Owner.Raw, tok.Owner.Raw, now) - if err != nil { - return errors.Wrap(err, "failed to verify transfer from htlc script") - } - - // check metadata - sigma := ctx.Signatures[i] - metadataKey, err := htlc2.MetadataClaimKeyCheck(ctx.TransferAction, script, op, sigma) - if err != nil { - return errors.WithMessagef(err, "failed to check htlc metadata") - } - if op != htlc2.Reclaim { - ctx.CountMetadataKey(metadataKey) - } - } - } - - for _, o := range ctx.TransferAction.GetOutputs() { - out, ok := o.(*Output) - if !ok { - return errors.New("invalid output") - } - if out.IsRedeem() { - continue - } - - // if it is an htlc script then the deadline must still be valid - owner, err := identity.UnmarshalTypedIdentity(out.Output.Owner.Raw) - if err != nil { - return err - } - if owner.Type == htlc.ScriptType { - script := &htlc.Script{} - err = json.Unmarshal(owner.Identity, script) - if err != nil { - return err - } - if err := script.Validate(now); err != nil { - return errors.WithMessagef(err, "htlc script invalid") - } - metadataKey, err := htlc2.MetadataLockKeyCheck(ctx.TransferAction, script) - if err != nil { - return errors.WithMessagef(err, "failed to check htlc metadata") - } - ctx.CountMetadataKey(metadataKey) - continue - } - } - return nil -} diff --git a/token/core/tms.go b/token/core/tms.go index 7d1ab5ca8..c3ebb31e2 100644 --- a/token/core/tms.go +++ b/token/core/tms.go @@ -168,6 +168,10 @@ func (m *TMSProvider) Configurations() ([]driver.Configuration, error) { return res, nil } +func (m *TMSProvider) PublicParametersFromBytes(raw []byte) (driver.PublicParameters, error) { + return m.tokenDriverService.PublicParametersFromBytes(raw) +} + func (m *TMSProvider) SetCallback(callback CallbackFunc) { m.callback = callback } diff --git a/token/core/zkatdlog/crypto/audit/auditor.go b/token/core/zkatdlog/crypto/audit/auditor.go index 02c640fd0..27b8997e2 100644 --- a/token/core/zkatdlog/crypto/audit/auditor.go +++ b/token/core/zkatdlog/crypto/audit/auditor.go @@ -19,9 +19,9 @@ import ( "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/transfer" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/interop/htlc" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp" - htlc2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" "github.com/pkg/errors" "go.opentelemetry.io/otel/trace" ) @@ -278,15 +278,17 @@ func InspectTokenOwner(des Deserializer, token *AuditableToken, index int) error return errors.Wrapf(err, "owner at index [%d] does not match the provided opening", index) } return nil - case htlc2.ScriptType: - return inspectTokenOwnerOfScript(des, token, index) + case htlc.ScriptType: + return inspectTokenOwnerOfHTLCScript(des, token, index) + case pledge.ScriptType: + return inspectTokenOwnerOfPledgeScript(des, token, index) default: return errors.Errorf("identity type [%s] not recognized", ro.Type) } } -func inspectTokenOwnerOfScript(des Deserializer, token *AuditableToken, index int) error { +func inspectTokenOwnerOfHTLCScript(des Deserializer, token *AuditableToken, index int) error { owner, err := identity.UnmarshalTypedIdentity(token.Token.Owner) if err != nil { return errors.Errorf("input owner at index [%d] cannot be unmarshalled", index) @@ -327,6 +329,45 @@ func inspectTokenOwnerOfScript(des Deserializer, token *AuditableToken, index in return nil } +func inspectTokenOwnerOfPledgeScript(des Deserializer, token *AuditableToken, index int) error { + owner, err := identity.UnmarshalTypedIdentity(token.Token.Owner) + if err != nil { + return errors.Errorf("input owner at index [%d] cannot be unmarshalled", index) + } + scriptInf := &htlc.ScriptInfo{} + if err := json.Unmarshal(token.Owner.OwnerInfo, scriptInf); err != nil { + return errors.Wrapf(err, "failed to unmarshal script info") + } + scriptSender, _, scriptIssuer, err := pledge.GetScriptSenderAndRecipient(owner) + if err != nil { + return errors.Wrap(err, "failed getting script sender and recipient") + } + + sender, err := des.GetOwnerMatcher(scriptInf.Sender) + if err != nil { + return errors.Wrapf(err, "failed to get owner matcher from pledge script sender [%s]", string(scriptInf.Sender)) + } + ro, err := identity.UnmarshalTypedIdentity(scriptSender) + if err != nil { + return errors.Wrapf(err, "failed to retrieve raw owner from sender in pledge script") + } + // If this is a reclaim, match it against the sender. + // If this is a redeem, match it against the issuer. + if err := sender.Match(ro.Identity); err != nil { + // Check if this can be matched to the issuer + ro, err := identity.UnmarshalTypedIdentity(scriptIssuer) + if err != nil { + return errors.Wrapf(err, "failed to retrieve raw owner from issuer in pledge script") + } + if err := sender.Match(ro.Identity); err != nil { + return errors.Wrapf(err, "token at index [%d] does not match the provided opening [%s]", index, string(scriptInf.Sender)) + } + } + + // TODO: recipient is in another network + return nil +} + // GetAuditInfoForIssues returns an array of AuditableToken for each issue action // It takes a deserializer, an array of serialized issue actions and an array of issue metadata. func GetAuditInfoForIssues(issues [][]byte, metadata []driver.IssueMetadata) ([][]*AuditableToken, error) { diff --git a/token/core/zkatdlog/crypto/validator/validator.go b/token/core/zkatdlog/crypto/validator/validator.go index cacadd898..b5d6c2ddd 100644 --- a/token/core/zkatdlog/crypto/validator/validator.go +++ b/token/core/zkatdlog/crypto/validator/validator.go @@ -8,6 +8,8 @@ package validator import ( "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/interop/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/interop/pledge" "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/logging" "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto" "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/issue" @@ -52,12 +54,14 @@ func New(logger logging.Logger, pp *crypto.PublicParams, deserializer driver.Des transferValidators := []ValidateTransferFunc{ TransferSignatureValidate, TransferZKProofValidate, - TransferHTLCValidate, + htlc.TransferHTLCValidate[*crypto.PublicParams, *token.Token, *transfer.Action, *issue.IssueAction, driver.Deserializer], + pledge.TransferPledgeValidate[*crypto.PublicParams, *token.Token, *transfer.Action, *issue.IssueAction, driver.Deserializer], } transferValidators = append(transferValidators, extraValidators...) issueValidators := []ValidateIssueFunc{ IssueValidate, + pledge.IssuePledgeValidate[*crypto.PublicParams, *token.Token, *transfer.Action, *issue.IssueAction, driver.Deserializer], } return common.NewValidator[*crypto.PublicParams, *token.Token, *transfer.Action, *issue.IssueAction, driver.Deserializer]( diff --git a/token/core/zkatdlog/crypto/validator/validator_transfer.go b/token/core/zkatdlog/crypto/validator/validator_transfer.go index b86ccf328..9462d58bb 100644 --- a/token/core/zkatdlog/crypto/validator/validator_transfer.go +++ b/token/core/zkatdlog/crypto/validator/validator_transfer.go @@ -7,16 +7,9 @@ SPDX-License-Identifier: Apache-2.0 package validator import ( - "encoding/json" - "time" - math "github.com/IBM/mathlib" - "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/token" "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/transfer" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" - htlc2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/interop/htlc" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" "github.com/pkg/errors" ) @@ -63,68 +56,3 @@ func TransferZKProofValidate(ctx *Context) error { return nil } - -func TransferHTLCValidate(ctx *Context) error { - now := time.Now() - - for i, in := range ctx.InputTokens { - owner, err := identity.UnmarshalTypedIdentity(in.Owner) - if err != nil { - return errors.Wrap(err, "failed to unmarshal owner of input token") - } - if owner.Type == htlc.ScriptType { - if len(ctx.InputTokens) != 1 || len(ctx.TransferAction.GetOutputs()) != 1 { - return errors.Errorf("invalid transfer action: an htlc script only transfers the ownership of a token") - } - - out := ctx.TransferAction.GetOutputs()[0].(*token.Token) - - // check that owner field in output is correct - script, op, err := htlc2.VerifyOwner(ctx.InputTokens[0].Owner, out.Owner, now) - if err != nil { - return errors.Wrap(err, "failed to verify transfer from htlc script") - } - - // check metadata - sigma := ctx.Signatures[i] - metadataKey, err := htlc2.MetadataClaimKeyCheck(ctx.TransferAction, script, op, sigma) - if err != nil { - return errors.WithMessagef(err, "failed to check htlc metadata") - } - if op != htlc2.Reclaim { - ctx.CountMetadataKey(metadataKey) - } - } - } - - for _, o := range ctx.TransferAction.GetOutputs() { - out, ok := o.(*token.Token) - if !ok { - return errors.Errorf("invalid output") - } - if out.IsRedeem() { - continue - } - owner, err := identity.UnmarshalTypedIdentity(out.Owner) - if err != nil { - return err - } - if owner.Type == htlc.ScriptType { - script := &htlc.Script{} - err = json.Unmarshal(owner.Identity, script) - if err != nil { - return err - } - if err := script.Validate(now); err != nil { - return errors.WithMessagef(err, "htlc script invalid") - } - metadataKey, err := htlc2.MetadataLockKeyCheck(ctx.TransferAction, script) - if err != nil { - return errors.WithMessagef(err, "failed to check htlc metadata") - } - ctx.CountMetadataKey(metadataKey) - continue - } - } - return nil -} diff --git a/token/core/zkatdlog/nogh/driver/deserializer.go b/token/core/zkatdlog/nogh/driver/deserializer.go index 5c43abbbc..10551b322 100644 --- a/token/core/zkatdlog/nogh/driver/deserializer.go +++ b/token/core/zkatdlog/nogh/driver/deserializer.go @@ -11,11 +11,11 @@ import ( "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto" "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/token" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/deserializer" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/interop/htlc" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/idemix" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/x509" - htlc2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" "github.com/pkg/errors" ) @@ -35,7 +35,8 @@ func NewDeserializer(pp *crypto.PublicParams) (*Deserializer, error) { } m := deserializer.NewTypedVerifierDeserializerMultiplex(idemixDes) m.AddTypedVerifierDeserializer(msp.IdemixIdentity, deserializer.NewTypedIdentityVerifierDeserializer(idemixDes)) - m.AddTypedVerifierDeserializer(htlc2.ScriptType, htlc.NewTypedIdentityDeserializer(m)) + m.AddTypedVerifierDeserializer(htlc.ScriptType, htlc.NewTypedIdentityDeserializer(m)) + m.AddTypedVerifierDeserializer(pledge.ScriptType, pledge.NewTypedIdentityDeserializer(m)) return &Deserializer{ Deserializer: common.NewDeserializer( @@ -80,6 +81,7 @@ type EIDRHDeserializer = deserializer.EIDRHDeserializer func NewEIDRHDeserializer() *EIDRHDeserializer { d := deserializer.NewEIDRHDeserializer() d.AddDeserializer(msp.IdemixIdentity, &idemix.AuditInfoDeserializer{}) - d.AddDeserializer(htlc2.ScriptType, htlc.NewAuditDeserializer(&idemix.AuditInfoDeserializer{})) + d.AddDeserializer(htlc.ScriptType, htlc.NewAuditDeserializer(&idemix.AuditInfoDeserializer{})) + d.AddDeserializer(pledge.ScriptType, pledge.NewAuditDeserializer(&idemix.AuditInfoDeserializer{})) return d } diff --git a/token/core/zkatdlog/nogh/driver/driver.go b/token/core/zkatdlog/nogh/driver/driver.go index 8658e76df..f858a9cf8 100644 --- a/token/core/zkatdlog/nogh/driver/driver.go +++ b/token/core/zkatdlog/nogh/driver/driver.go @@ -12,16 +12,19 @@ import ( view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/server/view" tracing2 "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/tracing" "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/interop/pledge" "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/logging" "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/metrics" "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/observables" "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto" token3 "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/token" zkatdlog "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/nogh" + _ "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/nogh/driver/interop/state/fabric" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" "github.com/hyperledger-labs/fabric-token-sdk/token/services/config" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" + pledge2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" "github.com/hyperledger-labs/fabric-token-sdk/token/services/network" "github.com/pkg/errors" "go.opentelemetry.io/otel/trace" @@ -109,6 +112,7 @@ func (d *Driver) NewTokenService(_ driver.ServiceProvider, networkID string, cha authorization := common.NewAuthorizationMultiplexer( common.NewTMSAuthorization(logger, ppm.PublicParams(), ws), htlc.NewScriptAuth(ws), + pledge2.NewScriptAuth(ws), ) metricsProvider := metrics.NewTMSProvider(tmsConfig.ID(), d.metricsProvider) @@ -123,7 +127,9 @@ func (d *Driver) NewTokenService(_ driver.ServiceProvider, networkID string, cha deserializer, tmsConfig, observables.NewObservableIssueService( - zkatdlog.NewIssueService(ppm, ws, deserializer, driverMetrics), + zkatdlog.NewIssueService(ppm, ws, deserializer, driverMetrics, []zkatdlog.IssueMetadataProviderFunc{ + pledge.IssueActionMetadata, + }), observables.NewIssue(tracerProvider), ), observables.NewObservableTransferService( diff --git a/token/core/zkatdlog/nogh/driver/interop/state/fabric/state.go b/token/core/zkatdlog/nogh/driver/interop/state/fabric/state.go new file mode 100644 index 000000000..d2c45da5c --- /dev/null +++ b/token/core/zkatdlog/nogh/driver/interop/state/fabric/state.go @@ -0,0 +1,86 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fabric + +import ( + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/logging" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" + fabric3 "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state/fabric" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state/fabric/core" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/network/common/rws/keys" + "go.uber.org/dig" +) + +type Validator struct{} + +func (v *Validator) Validate(tokRaw []byte, info *pledge.Info) error { + // TODO: complete this + // tok := &token.Token{} + // err := json.Unmarshal(tokRaw, tok) + // if err != nil { + // return errors.Wrapf(err, "failed to unmarshal token [%s]", common.Hashable(tokRaw)) + // } + // + // if tok.Type != info.TokenType { + // return errors.Errorf("type of pledge token does not match type in claim request") + // } + // q, err := token.ToQuantity(tok.Quantity, 64) + // if err != nil { + // return errors.Wrapf(err, "failed converting token quantity [%s]", tok.Quantity) + // } + // expectedQ := token.NewQuantityFromUInt64(info.Amount) + // if expectedQ.Cmp(q) != 0 { + // return errors.Errorf("quantity in pledged token is different from quantity in claim request") + // } + // owner, err := identity.UnmarshalTypedIdentity(tok.Owner.Raw) + // if err != nil { + // return errors.Wrapf(err, "failed to unmarshal owner of token [%s]", common.Hashable(tokRaw)) + // } + // if owner.Type != pledge.ScriptType { + // return err + // } + // script := &pledge.Script{} + // err = json.Unmarshal(owner.Identity, script) + // if err != nil { + // return errors.Wrapf(err, "failed to unmarshal pledge script [%s]", common.Hashable(tokRaw)) + // } + // if script.Recipient == nil { + // return errors.Errorf("script in proof encodes invalid recipient") + // } + // if !script.Recipient.Equal(info.Script.Recipient) { + // return errors.Errorf("recipient in claim request does not match recipient in proof") + // } + // if script.Deadline != info.Script.Deadline { + // return errors.Errorf("deadline in claim request does not match deadline in proof") + // } + // if script.DestinationNetwork != info.Script.DestinationNetwork { + // return errors.Errorf("destination network in claim request does not match destination network in proof [%s vs.%s]", info.Script.DestinationNetwork, script.DestinationNetwork) + // } + + return nil +} + +func NewStateDriver(in struct { + dig.In + FNSProvider *fabric.NetworkServiceProvider + RelayProvider fabric3.RelayProvider + VaultStore *pledge.VaultStore +}) fabric3.NamedStateDriver { + return fabric3.NamedStateDriver{ + Name: crypto.DLogPublicParameters, + Driver: core.NewStateDriver( + logging.MustGetLogger("token-sdk.core.zkatdlog"), + in.FNSProvider, + in.RelayProvider, + in.VaultStore, + &Validator{}, + &keys.Translator{}, + ), + } +} diff --git a/token/core/zkatdlog/nogh/issue.go b/token/core/zkatdlog/nogh/issue.go index 37dffb982..0c4de1de9 100644 --- a/token/core/zkatdlog/nogh/issue.go +++ b/token/core/zkatdlog/nogh/issue.go @@ -18,10 +18,13 @@ import ( "github.com/pkg/errors" ) +type IssueMetadataProviderFunc = func(attributes map[string][]byte, opts *driver.IssueOptions) (map[string][]byte, error) + type IssueService struct { PublicParametersManager common2.PublicParametersManager[*crypto.PublicParams] WalletService driver.WalletService Deserializer driver.Deserializer + IssueMetadataProviders []IssueMetadataProviderFunc Metrics *Metrics } @@ -30,12 +33,14 @@ func NewIssueService( walletService driver.WalletService, deserializer driver.Deserializer, metrics *Metrics, + IssueMetadataProviders []IssueMetadataProviderFunc, ) *IssueService { return &IssueService{ PublicParametersManager: publicParametersManager, WalletService: walletService, Deserializer: deserializer, Metrics: metrics, + IssueMetadataProviders: IssueMetadataProviders, } } @@ -74,6 +79,15 @@ func (s *IssueService) Issue(ctx context.Context, issuerIdentity driver.Identity } s.Metrics.zkIssueDuration.Observe(float64(duration.Milliseconds())) + actionMetadata := map[string][]byte{} + for _, provider := range s.IssueMetadataProviders { + actionMetadata, err = provider(actionMetadata, opts) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed generating token action metadata") + } + } + action.Metadata = actionMetadata + var outputsMetadata [][]byte for _, meta := range zkOutputsMetadata { raw, err := meta.Serialize() diff --git a/token/driver/action.go b/token/driver/action.go index 161cbcc80..378e440b9 100644 --- a/token/driver/action.go +++ b/token/driver/action.go @@ -41,6 +41,8 @@ type Output interface { Serialize() ([]byte, error) // IsRedeem returns true if the output is a redeem output IsRedeem() bool + // GetOwner returns the owner of this token + GetOwner() []byte } //go:generate counterfeiter -o mock/ta.go -fake-name TransferAction . TransferAction diff --git a/token/driver/tms.go b/token/driver/tms.go index 9a767e2a4..90fff8bf4 100644 --- a/token/driver/tms.go +++ b/token/driver/tms.go @@ -96,4 +96,6 @@ type TokenManagerServiceProvider interface { Update(options ServiceOptions) error Configurations() ([]Configuration, error) + + PublicParametersFromBytes(raw []byte) (PublicParameters, error) } diff --git a/token/provider.go b/token/provider.go index 839ef0b02..c61aa2a12 100644 --- a/token/provider.go +++ b/token/provider.go @@ -93,6 +93,14 @@ func (p *ManagementServiceProvider) NewManagementService(opts ...ServiceOption) return p.managementService(true, opts...) } +func (p *ManagementServiceProvider) PublicParametersFromBytes(raw []byte) (*PublicParameters, error) { + pp, err := p.tmsProvider.PublicParametersFromBytes(raw) + if err != nil { + return nil, errors.WithMessagef(err, "failed unmarshalling public parameters") + } + return &PublicParameters{PublicParameters: pp}, nil +} + func (p *ManagementServiceProvider) managementService(aNew bool, opts ...ServiceOption) (*ManagementService, error) { opt, err := CompileServiceOptions(opts...) if err != nil { diff --git a/token/publicparams.go b/token/publicparams.go index 124d4ed28..843148369 100644 --- a/token/publicparams.go +++ b/token/publicparams.go @@ -14,7 +14,6 @@ type PPHash = driver.PPHash type PublicParameters struct { driver.PublicParameters - ppm driver.PublicParamsManager } // Precision returns the precision used to represent the token quantity @@ -74,7 +73,7 @@ func (c *PublicParametersManager) PublicParameters() *PublicParameters { if pp == nil { return nil } - return &PublicParameters{PublicParameters: pp, ppm: c.ppm} + return &PublicParameters{PublicParameters: pp} } func (c *PublicParametersManager) PublicParamsHash() PPHash { diff --git a/token/sdk/dig/sdk.go b/token/sdk/dig/sdk.go index 9113f2c45..df4c3f07c 100644 --- a/token/sdk/dig/sdk.go +++ b/token/sdk/dig/sdk.go @@ -239,6 +239,7 @@ func registerNetworkDrivers(in struct { NetworkProvider *network.Provider Drivers []driver3.Driver `group:"network-drivers"` }) { + logger.Debugf("registering [%d] network drivers", len(in.Drivers)) for _, d := range in.Drivers { in.NetworkProvider.RegisterDriver(d) } diff --git a/token/services/identity/deserializer/typed.go b/token/services/identity/deserializer/typed.go index 2a6324d62..0eed2acc5 100644 --- a/token/services/identity/deserializer/typed.go +++ b/token/services/identity/deserializer/typed.go @@ -8,7 +8,7 @@ package deserializer import ( "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/hash" - "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" + "github.com/hyperledger-labs/fabric-token-sdk/token" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" "github.com/hyperledger-labs/fabric-token-sdk/token/services/logging" @@ -18,10 +18,18 @@ import ( var logger = logging.MustGetLogger("token-sdk.services.identity.deserializer") +// VerifierDeserializer is the interface for verifiers' deserializer. +// A verifier checks the validity of a signature against the identity associated with the verifier +type VerifierDeserializer interface { + DeserializeVerifier(id driver.Identity) (driver.Verifier, error) +} + +type AuditInfoProvider = driver.AuditInfoProvider + type TypedVerifierDeserializer interface { - DeserializeVerifier(typ string, raw []byte) (driver.Verifier, error) - Recipients(id driver.Identity, typ string, raw []byte) ([]driver.Identity, error) - GetOwnerAuditInfo(id driver.Identity, typ string, raw []byte, p driver.AuditInfoProvider) ([][]byte, error) + DeserializeVerifier(typ string, raw []byte) (token.Verifier, error) + Recipients(id token.Identity, typ string, raw []byte) ([]token.Identity, error) + GetOwnerAuditInfo(id token.Identity, typ string, raw []byte, p AuditInfoProvider) ([][]byte, error) } // AuditMatcherDeserializer deserializes raw bytes into a matcher, which allows an auditor to match an identity to an enrollment ID @@ -83,9 +91,9 @@ func (v *TypedVerifierDeserializerMultiplex) MatchOwnerIdentity(id driver.Identi if err != nil { return errors.Wrapf(err, "failed to unmarshal identity [%s]", id) } - //if recipient.Type != v.identityType { + // if recipient.Type != v.identityType { // return errors.Errorf("expected serialized identity type, got [%s]", recipient.Type) - //} + // } matcher, err := v.GetOwnerMatcher(ai) if err != nil { @@ -115,10 +123,10 @@ func (v *TypedVerifierDeserializerMultiplex) GetOwnerAuditInfo(id driver.Identit } type TypedIdentityVerifierDeserializer struct { - common.VerifierDeserializer + VerifierDeserializer } -func NewTypedIdentityVerifierDeserializer(verifierDeserializer common.VerifierDeserializer) *TypedIdentityVerifierDeserializer { +func NewTypedIdentityVerifierDeserializer(verifierDeserializer VerifierDeserializer) *TypedIdentityVerifierDeserializer { return &TypedIdentityVerifierDeserializer{VerifierDeserializer: verifierDeserializer} } diff --git a/token/services/interop/audit.go b/token/services/interop/audit.go new file mode 100644 index 000000000..c9ca42d3b --- /dev/null +++ b/token/services/interop/audit.go @@ -0,0 +1,139 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package interop + +import ( + "encoding/json" + + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" + "github.com/pkg/errors" +) + +type Input struct { + *token.Input + isHTLC bool + isPledge bool +} + +func ToInput(i *token.Input) (*Input, error) { + owner, err := identity.UnmarshalTypedIdentity(i.Owner) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal owner") + } + return &Input{ + Input: i, + isHTLC: owner.Type == htlc.ScriptType, + isPledge: owner.Type == pledge.ScriptType, + }, nil +} + +func (i *Input) IsHTLC() bool { + return i.isHTLC +} + +func (i *Input) IsPledge() bool { + return i.isPledge +} + +func (i *Input) HTLC() (*htlc.Script, error) { + if !i.isHTLC { + return nil, errors.New("this input does not refer to an HTLC script") + } + owner, err := identity.UnmarshalTypedIdentity(i.Owner) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal owner") + } + script := &htlc.Script{} + err = json.Unmarshal(owner.Identity, script) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal HTLC script") + } + return script, nil +} + +func (i *Input) Pledge() (*pledge.Script, error) { + if !i.isPledge { + return nil, errors.New("this input does not refer to a pledge script") + } + owner, err := identity.UnmarshalTypedIdentity(i.Owner) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal owner") + } + script := &pledge.Script{} + err = json.Unmarshal(owner.Identity, script) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal pledge script") + } + return script, nil +} + +type Output struct { + *token.Output + isHTLC bool + isPledge bool +} + +func ToOutput(o *token.Output) (*Output, error) { + if o.Owner != nil { + owner, err := identity.UnmarshalTypedIdentity(o.Owner) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal owner") + } + return &Output{ + Output: o, + isHTLC: owner.Type == htlc.ScriptType, + isPledge: owner.Type == pledge.ScriptType, + }, nil + } + return &Output{ + Output: o, + }, nil + +} + +func (o *Output) IsHTLC() bool { + return o.isHTLC +} + +func (o *Output) IsPledge() bool { + return o.isPledge +} + +func (o *Output) HTLC() (*htlc.Script, error) { + if !o.isHTLC { + return nil, errors.New("this output does not refer to an HTLC script") + } + owner, err := identity.UnmarshalTypedIdentity(o.Owner) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal owner") + } + script := &htlc.Script{} + err = json.Unmarshal(owner.Identity, script) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmrshal HTLC script") + } + return script, nil +} + +func (o *Output) Pledge() (*pledge.Script, error) { + if !o.isPledge { + return nil, errors.New("this output does not refer to a pledge script") + } + owner, err := identity.UnmarshalTypedIdentity(o.Owner) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal owner") + } + script := &pledge.Script{} + err = json.Unmarshal(owner.Identity, script) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmrshal pledge script") + } + return script, nil +} diff --git a/token/services/interop/htlc/audit.go b/token/services/interop/htlc/audit.go deleted file mode 100644 index 421aef818..000000000 --- a/token/services/interop/htlc/audit.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package htlc - -import ( - "encoding/json" - - "github.com/hyperledger-labs/fabric-token-sdk/token" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" - "github.com/pkg/errors" -) - -type Input struct { - *token.Input - isHTLC bool -} - -func ToInput(i *token.Input) (*Input, error) { - owner, err := identity.UnmarshalTypedIdentity(i.Owner) - if err != nil { - return nil, errors.Wrapf(err, "failed to unmarshal owner") - } - return &Input{ - Input: i, - isHTLC: owner.Type == ScriptType, - }, nil -} - -func (i *Input) IsHTLC() bool { - return i.isHTLC -} - -func (i *Input) Script() (*Script, error) { - if !i.isHTLC { - return nil, errors.New("this input does not refer to an HTLC script") - } - - owner, err := identity.UnmarshalTypedIdentity(i.Owner) - if err != nil { - return nil, errors.Wrapf(err, "failed to unmarshal owner") - } - if owner.Type != ScriptType { - return nil, errors.Errorf("invalid identity type, expected [%s], got [%s]", ScriptType, owner.Type) - } - script := &Script{} - err = json.Unmarshal(owner.Identity, script) - if err != nil { - return nil, errors.Wrapf(err, "failed to unmrshal HTLC script") - } - return script, nil -} - -type Output struct { - *token.Output - isHTLC bool -} - -func ToOutput(i *token.Output) (*Output, error) { - owner, err := identity.UnmarshalTypedIdentity(i.Owner) - if err != nil { - return nil, errors.Wrapf(err, "failed to unmarshal owner") - } - return &Output{ - Output: i, - isHTLC: owner.Type == ScriptType, - }, nil -} - -func (o *Output) IsHTLC() bool { - return o.isHTLC -} - -func (o *Output) Script() (*Script, error) { - if !o.isHTLC { - return nil, errors.New("this output does not refer to an HTLC script") - } - - owner, err := identity.UnmarshalTypedIdentity(o.Owner) - if err != nil { - return nil, errors.Wrapf(err, "failed to unmarshal owner") - } - if owner.Type != ScriptType { - return nil, errors.Errorf("invalid identity type, expected [%s], got [%s]", ScriptType, owner.Type) - } - script := &Script{} - err = json.Unmarshal(owner.Identity, script) - if err != nil { - return nil, errors.Wrapf(err, "failed to unmrshal HTLC script") - } - return script, nil -} diff --git a/token/services/identity/interop/htlc/deserializer.go b/token/services/interop/htlc/deserializer.go similarity index 79% rename from token/services/identity/interop/htlc/deserializer.go rename to token/services/interop/htlc/deserializer.go index 510c61e97..a6f08d0b6 100644 --- a/token/services/identity/interop/htlc/deserializer.go +++ b/token/services/interop/htlc/deserializer.go @@ -9,14 +9,14 @@ package htlc import ( "encoding/json" - "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/deserializer" driver2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/driver" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" "github.com/pkg/errors" ) type VerifierDES interface { - DeserializeVerifier(id driver.Identity) (driver.Verifier, error) + DeserializeVerifier(id token.Identity) (token.Verifier, error) } type TypedIdentityDeserializer struct { @@ -27,17 +27,17 @@ func NewTypedIdentityDeserializer(verifierDeserializer VerifierDES) *TypedIdenti return &TypedIdentityDeserializer{VerifierDeserializer: verifierDeserializer} } -func (t *TypedIdentityDeserializer) DeserializeVerifier(typ string, raw []byte) (driver.Verifier, error) { - if typ != htlc.ScriptType { - return nil, errors.Errorf("cannot deserializer type [%s], expected [%s]", typ, htlc.ScriptType) +func (t *TypedIdentityDeserializer) DeserializeVerifier(typ string, raw []byte) (token.Verifier, error) { + if typ != ScriptType { + return nil, errors.Errorf("cannot deserializer type [%s], expected [%s]", typ, ScriptType) } - script := &htlc.Script{} + script := &Script{} err := json.Unmarshal(raw, script) if err != nil { return nil, errors.Errorf("failed to unmarshal TypedIdentity as an htlc script") } - v := &htlc.Verifier{} + v := &Verifier{} v.Sender, err = t.VerifierDeserializer.DeserializeVerifier(script.Sender) if err != nil { return nil, errors.Errorf("failed to unmarshal the identity of the sender in the htlc script") @@ -53,24 +53,24 @@ func (t *TypedIdentityDeserializer) DeserializeVerifier(typ string, raw []byte) return v, nil } -func (t *TypedIdentityDeserializer) Recipients(id driver.Identity, typ string, raw []byte) ([]driver.Identity, error) { - if typ != htlc.ScriptType { +func (t *TypedIdentityDeserializer) Recipients(id token.Identity, typ string, raw []byte) ([]token.Identity, error) { + if typ != ScriptType { return nil, errors.New("unknown identity type") } - script := &htlc.Script{} + script := &Script{} err := json.Unmarshal(raw, script) if err != nil { return nil, errors.Wrapf(err, "failed to unmarshal htlc script") } - return []driver.Identity{script.Recipient}, nil + return []token.Identity{script.Recipient}, nil } -func (t *TypedIdentityDeserializer) GetOwnerAuditInfo(id driver.Identity, typ string, raw []byte, p driver.AuditInfoProvider) ([][]byte, error) { - if typ != htlc.ScriptType { - return nil, errors.Errorf("invalid type, got [%s], expected [%s]", typ, htlc.ScriptType) +func (t *TypedIdentityDeserializer) GetOwnerAuditInfo(id token.Identity, typ string, raw []byte, p deserializer.AuditInfoProvider) ([][]byte, error) { + if typ != ScriptType { + return nil, errors.Errorf("invalid type, got [%s], expected [%s]", typ, ScriptType) } - script := &htlc.Script{} + script := &Script{} var err error err = json.Unmarshal(raw, script) if err != nil { diff --git a/token/services/identity/interop/htlc/info.go b/token/services/interop/htlc/info.go similarity index 76% rename from token/services/identity/interop/htlc/info.go rename to token/services/interop/htlc/info.go index bdfa05af5..0869b54be 100644 --- a/token/services/identity/interop/htlc/info.go +++ b/token/services/interop/htlc/info.go @@ -9,14 +9,13 @@ package htlc import ( "encoding/json" - "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + "github.com/hyperledger-labs/fabric-token-sdk/token" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" "github.com/pkg/errors" ) type AuditInfoProvider interface { - GetAuditInfo(identity driver.Identity) ([]byte, error) + GetAuditInfo(identity token.Identity) ([]byte, error) } // ScriptInfo includes info about the sender and the recipient @@ -34,9 +33,9 @@ func (si *ScriptInfo) Unmarshal(raw []byte) error { } // GetScriptSenderAndRecipient returns the script's sender and recipient according to the type of the given owner -func GetScriptSenderAndRecipient(ro *identity.TypedIdentity) (sender, recipient driver.Identity, err error) { - if ro.Type == htlc.ScriptType { - script := &htlc.Script{} +func GetScriptSenderAndRecipient(ro *identity.TypedIdentity) (sender, recipient token.Identity, err error) { + if ro.Type == ScriptType { + script := &Script{} err = json.Unmarshal(ro.Identity, script) if err != nil { return nil, nil, errors.Wrapf(err, "failed to unmarshal htlc script") diff --git a/token/services/interop/htlc/script.go b/token/services/interop/htlc/script.go index 2818a09ab..061a4dca6 100644 --- a/token/services/interop/htlc/script.go +++ b/token/services/interop/htlc/script.go @@ -74,18 +74,28 @@ type Script struct { // - The deadline must be after the passed time reference // - HashInfo must be Available func (s *Script) Validate(timeReference time.Time) error { + if err := s.WellFormedness(); err != nil { + return err + } + if s.Deadline.Before(timeReference) { + return errors.New("expiration date has already passed") + } + return nil +} + +func (s *Script) WellFormedness() error { if s.Sender.IsNone() { return errors.New("sender not set") } if s.Recipient.IsNone() { return errors.New("recipient not set") } - if s.Deadline.Before(timeReference) { - return errors.New("expiration date has already passed") - } if err := s.HashInfo.Validate(); err != nil { return err } + if len(s.HashInfo.Hash) == 0 { + return errors.New("hash is not set") + } return nil } diff --git a/token/services/interop/htlc/signer.go b/token/services/interop/htlc/signer.go index e9036b550..b71f13eaf 100644 --- a/token/services/interop/htlc/signer.go +++ b/token/services/interop/htlc/signer.go @@ -11,7 +11,7 @@ import ( "encoding/json" "time" - "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + "github.com/hyperledger-labs/fabric-token-sdk/token" "github.com/pkg/errors" ) @@ -23,7 +23,7 @@ type ClaimSignature struct { // ClaimSigner is the signer for the claim of an htlc script type ClaimSigner struct { - Recipient driver.Signer + Recipient token.Signer Preimage []byte } @@ -51,7 +51,7 @@ func concatTokenRequestTxIDPreimage(tokenRequestAndTxID []byte, preImage []byte) // ClaimVerifier is the verifier of a ClaimSignature type ClaimVerifier struct { - Recipient driver.Verifier + Recipient token.Verifier HashInfo HashInfo } @@ -87,8 +87,8 @@ func (cv *ClaimVerifier) Verify(tokenRequestAndTxID, claimSignature []byte) erro // Verifier checks if an htlc script can be claimed or reclaimed type Verifier struct { - Recipient driver.Verifier - Sender driver.Verifier + Recipient token.Verifier + Sender token.Verifier Deadline time.Time HashInfo HashInfo } diff --git a/token/services/identity/interop/htlc/validator.go b/token/services/interop/htlc/validator.go similarity index 82% rename from token/services/identity/interop/htlc/validator.go rename to token/services/interop/htlc/validator.go index 3eb6c1382..d6c4d4cb0 100644 --- a/token/services/identity/interop/htlc/validator.go +++ b/token/services/interop/htlc/validator.go @@ -12,7 +12,6 @@ import ( "time" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" "github.com/pkg/errors" ) @@ -29,15 +28,15 @@ type Action interface { } // VerifyOwner validates the owners of the transfer in the htlc script -func VerifyOwner(senderRawOwner []byte, outRawOwner []byte, now time.Time) (*htlc.Script, OperationType, error) { +func VerifyOwner(senderRawOwner []byte, outRawOwner []byte, now time.Time) (*Script, OperationType, error) { sender, err := identity.UnmarshalTypedIdentity(senderRawOwner) if err != nil { return nil, None, err } - if sender.Type != htlc.ScriptType { - return nil, None, errors.Errorf("invalid identity type, expected [%s], got [%s]", htlc.ScriptType, sender.Type) + if sender.Type != ScriptType { + return nil, None, errors.Errorf("invalid identity type, expected [%s], got [%s]", ScriptType, sender.Type) } - script := &htlc.Script{} + script := &Script{} err = json.Unmarshal(sender.Identity, script) if err != nil { return nil, None, err @@ -59,14 +58,14 @@ func VerifyOwner(senderRawOwner []byte, outRawOwner []byte, now time.Time) (*htl } // MetadataClaimKeyCheck checks that the claim key is in place -func MetadataClaimKeyCheck(action Action, script *htlc.Script, op OperationType, sig []byte) (string, error) { +func MetadataClaimKeyCheck(action Action, script *Script, op OperationType, sig []byte) (string, error) { if op == Reclaim { // No metadata in this case return "", nil } // Unmarshal signature to ClaimSignature - claim := &htlc.ClaimSignature{} + claim := &ClaimSignature{} if err := json.Unmarshal(sig, claim); err != nil { return "", errors.Wrapf(err, "failed unmarshalling claim signature [%s]", string(sig)) } @@ -84,7 +83,7 @@ func MetadataClaimKeyCheck(action Action, script *htlc.Script, op OperationType, if err != nil { return "", errors.Wrapf(err, "failed to compute image of [%x]", claim.Preimage) } - key := htlc.ClaimKey(image) + key := ClaimKey(image) value, ok := metadata[key] if !ok { return "", errors.New("cannot find htlc pre-image, missing metadata entry") @@ -97,17 +96,17 @@ func MetadataClaimKeyCheck(action Action, script *htlc.Script, op OperationType, } // MetadataLockKeyCheck checks that the lock key is in place -func MetadataLockKeyCheck(action Action, script *htlc.Script) (string, error) { +func MetadataLockKeyCheck(action Action, script *Script) (string, error) { metadata := action.GetMetadata() if len(metadata) == 0 { return "", errors.New("cannot find htlc lock, no metadata") } - key := htlc.LockKey(script.HashInfo.Hash) + key := LockKey(script.HashInfo.Hash) value, ok := metadata[key] if !ok { return "", errors.New("cannot find htlc lock, missing metadata entry") } - if !bytes.Equal(value, htlc.LockValue(script.HashInfo.Hash)) { + if !bytes.Equal(value, LockValue(script.HashInfo.Hash)) { return "", errors.Errorf("invalid action, cannot match htlc lock with metadata [%x]!=[%x]", value, script.HashInfo.Hash) } return key, nil diff --git a/token/services/interop/pledge/accept.go b/token/services/interop/pledge/accept.go new file mode 100644 index 000000000..db6f7e999 --- /dev/null +++ b/token/services/interop/pledge/accept.go @@ -0,0 +1,17 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" +) + +// NewAcceptView returns an instance of the ttx acceptView struct +func NewAcceptView(tx *Transaction) view.View { + return ttx.NewAcceptView(tx.Transaction) +} diff --git a/token/services/interop/pledge/approve.go b/token/services/interop/pledge/approve.go new file mode 100644 index 000000000..1c771b5b6 --- /dev/null +++ b/token/services/interop/pledge/approve.go @@ -0,0 +1,275 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/hash" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/session" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + tokn "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/network" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" +) + +type IssuerApprovalRequest struct { + OriginTMSID tokn.TMSID + TokenID *token.ID + Proof []byte + Destination string + RequestorSignature []byte +} + +func (r *IssuerApprovalRequest) Bytes() ([]byte, error) { + return json.Marshal(r) +} + +func (r *IssuerApprovalRequest) FromBytes(raw []byte) error { + return json.Unmarshal(raw, r) +} + +type IssuerApprovalResponse struct { + Signature []byte +} + +func (r *IssuerApprovalResponse) Bytes() ([]byte, error) { + return json.Marshal(r) +} + +func (r *IssuerApprovalResponse) FromBytes(raw []byte) error { + return json.Unmarshal(raw, r) +} + +type RequestIssuerSignatureView struct { + originTMSID tokn.TMSID + sender view.Identity + issuer view.Identity + tokenID *token.ID + reclaimProof []byte + network string + pledgeID string +} + +func RequestIssuerSignature(context view.Context, tokenID *token.ID, originTMSID tokn.TMSID, script *Script, proof []byte) ([]byte, error) { + boxed, err := context.RunView(&RequestIssuerSignatureView{ + originTMSID: originTMSID, + sender: script.Sender, + issuer: script.Issuer, + tokenID: tokenID, + reclaimProof: proof, + network: script.DestinationNetwork, + pledgeID: script.ID, + }) + if err != nil { + return nil, err + } + return boxed.([]byte), nil +} + +func (v *RequestIssuerSignatureView) Call(context view.Context) (interface{}, error) { + logger.Debugf("RequestIssuerSignatureView:caller [%s]", context.Initiator()) + + session, err := context.GetSession(context.Initiator(), v.issuer) + if err != nil { + return nil, err + } + + // Ask for issuer's signature + req := &IssuerApprovalRequest{ + OriginTMSID: v.originTMSID, + TokenID: v.tokenID, + Destination: v.network, + Proof: v.reclaimProof, + } + + reqRaw, err := req.Bytes() + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling issuer signature request") + } + // sign request + logger.Debugf("sign request [%s]", v.sender) + signer, err := tokn.GetManagementService(context, tokn.WithTMSID(v.originTMSID)).SigService().GetSigner(v.sender) + if err != nil { + return nil, err + } + msg := append(reqRaw, v.sender.Bytes()...) + req.RequestorSignature, err = signer.Sign(msg) + if err != nil { + return nil, err + } + verifier, err := tokn.GetManagementService(context, tokn.WithTMSID(req.OriginTMSID)).SigService().OwnerVerifier(v.sender) + if err != nil { + return nil, errors.Wrapf(err, "failed to retrieve a verifier to check sender signature") + } + if err := verifier.Verify(msg, req.RequestorSignature); err != nil { + return nil, errors.Wrapf(err, "failed to double-verify sender signature") + } + + reqRaw, err = req.Bytes() + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling issuer signature request") + } + err = session.Send(reqRaw) + if err != nil { + return nil, err + } + + // Wait to receive a signature + ch := session.Receive() + var payload []byte + select { + case msg := <-ch: + payload = msg.Payload + if msg.Status == view.ERROR { + return nil, errors.Errorf("failed requesting approval [%s]", string(payload)) + } + case <-time.After(60 * time.Second): + return nil, errors.New("time out reached") + } + logger.Debugf("received approval response [%v]", payload) + + res := &IssuerApprovalResponse{} + err = res.FromBytes(payload) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal approval response [%s]", string(payload)) + } + // check if signature is valid + // TODO: The issuer here is identified with it is owner identity. Shall we have the issuer identity? + verifier, err = tokn.GetManagementService(context, tokn.WithTMSID(req.OriginTMSID)).SigService().OwnerVerifier(v.issuer) + if err != nil { + return nil, errors.Wrapf(err, "failed to retrieve a verifier to check issuer signature") + } + + err = verifier.Verify([]byte(v.pledgeID), res.Signature) + if err != nil { + return nil, errors.Wrapf(err, "invalid issuer signature") + } + return res.Signature, nil +} + +type RequestIssuerSignatureResponderView struct { + walletID string +} + +func RespondRequestIssuerSignature(context view.Context, walletID string) ([]byte, error) { + sig, err := context.RunView(&RequestIssuerSignatureResponderView{walletID: walletID}) + if err != nil { + return nil, err + } + return sig.([]byte), nil +} + +func (v *RequestIssuerSignatureResponderView) Call(context view.Context) (interface{}, error) { + s, payload, err := session.ReadFirstMessage(context) + if err != nil { + return nil, err + } + + req := &IssuerApprovalRequest{} + if err := req.FromBytes(payload); err != nil { + return nil, errors.Wrapf(err, "failed unmarshalling signature request") + } + w, err := GetIssuerWallet(context, v.walletID) + if err != nil { + return nil, err + } + + wallet := NewIssuerWallet(context, w) + _, script, err := wallet.GetPledgedToken(req.TokenID) + if err != nil { + return nil, err + } + // check validity of reclaim + if time.Now().Before(script.Deadline) { + return nil, errors.Errorf("cannot reclaim token yet; deadline has not elapsed yet") + } + if req.Destination != script.DestinationNetwork { + return nil, errors.Errorf("destination network in reclaim request does not match destination network in pledged token") + } + // access control check + logger.Debugf("verify request [%s]", script.Sender) + verifier, err := tokn.GetManagementService(context, tokn.WithTMSID(req.OriginTMSID)).SigService().OwnerVerifier(script.Sender) + if err != nil { + return nil, errors.Wrapf(err, "failed to retrieve a verifier to check sender signature") + } + request := &IssuerApprovalRequest{ + OriginTMSID: req.OriginTMSID, + TokenID: req.TokenID, + Proof: req.Proof, + Destination: req.Destination, + } + toBeVerified, err := json.Marshal(request) + if err != nil { + return nil, err + } + err = verifier.Verify(append(toBeVerified, script.Sender...), req.RequestorSignature) + if err != nil { + return nil, errors.Wrapf(err, "failed to verify reclaim request signature") + } + + // verify proof before returning it + net := network.GetInstance(context, req.OriginTMSID.Network, req.OriginTMSID.Channel) + if net == nil { + return nil, errors.Errorf("cannot find network for [%s]", req.OriginTMSID) + } + origin := net.InteropURL(req.OriginTMSID.Namespace) + + ssp, err := state.GetServiceProvider(context) + if err != nil { + return nil, errors.WithMessage(err, "failed getting state service provider") + } + stateProofVerifier, err := ssp.Verifier(script.DestinationNetwork) + if err != nil { + return nil, errors.WithMessagef(err, "failed getting verifier for [%s]", origin) + } + if err := stateProofVerifier.VerifyProofNonExistence(req.Proof, req.TokenID, origin, script.Deadline); err != nil { + return nil, errors.WithMessagef(err, "failed verifying proof of existence for [%s]", origin) + } + + // sign + signer, err := tokn.GetManagementService(context, tokn.WithTMSID(req.OriginTMSID)).SigService().GetSigner(script.Issuer) + if err != nil { + return nil, err + } + + sigma, err := signer.Sign([]byte(script.ID)) + if err != nil { + return nil, err + } + + ver, err := tokn.GetManagementService(context, tokn.WithTMSID(req.OriginTMSID)).SigService().OwnerVerifier(script.Issuer) + if err != nil { + return nil, err + } + if err := ver.Verify([]byte(script.ID), sigma); err != nil { + return nil, errors.Wrapf(err, "failed to verify issuer signature [%s]", script.Issuer) + } + + logger.Debugf("produced signature by (me) [%s,%s,%s]", + hash.Hashable(req.TokenID.String()).String(), + hash.Hashable(sigma).String(), + script.Issuer.UniqueID(), + ) + res := &IssuerApprovalResponse{Signature: sigma} + resRaw, err := res.Bytes() + if err != nil { + return nil, err + } + fmt.Printf("sent approval response [%v]\n", resRaw) + err = s.Send(resRaw) + if err != nil { + return nil, err + } + fmt.Printf("sent approval response [%v]\n", resRaw) + + return resRaw, nil +} diff --git a/token/services/interop/pledge/claim.go b/token/services/interop/pledge/claim.go new file mode 100644 index 000000000..f36949233 --- /dev/null +++ b/token/services/interop/pledge/claim.go @@ -0,0 +1,269 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + "time" + + view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/session" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/network" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" + token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" +) + +const ( + TokenIDKey = "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge/tokenID" + NetworkKey = "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge/network" + ProofKey = "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge/proof" +) + +func (t *Transaction) Claim(issuerWallet *token.IssuerWallet, typ string, value uint64, recipient view.Identity, originTokenID *token2.ID, originNetwork string, proof []byte) error { + if typ == "" { + return errors.Errorf("must specify a type") + } + if value == 0 { + return errors.Errorf("must specify a value") + } + if recipient.IsNone() { + return errors.Errorf("must specify a recipient") + } + if originTokenID == nil { + return errors.Errorf("must specify the origin token ID") + } + if originNetwork == "" { + return errors.Errorf("must specify the origin network") + } + if proof == nil { + return errors.Errorf("must provide a proof") + } + + _, err := t.TokenRequest.Issue( + t.Context, + issuerWallet, + recipient, + typ, + value, + WithMetadata(originTokenID, originNetwork, proof), + ) + return err +} + +func WithMetadata(tokenID *token2.ID, network string, proof []byte) token.IssueOption { + return func(options *token.IssueOptions) error { + if options.Attributes == nil { + options.Attributes = make(map[interface{}]interface{}) + } + options.Attributes[TokenIDKey] = tokenID + options.Attributes[NetworkKey] = network + options.Attributes[ProofKey] = proof + return nil + } +} + +type ClaimRequest struct { + TokenType string + Quantity uint64 + Recipient view.Identity + RecipientAuditInfo []byte + ClaimDeadline time.Time + OriginTokenID *token2.ID + OriginNetwork string + PledgeProof []byte + RequestorSignature []byte +} + +func (cr *ClaimRequest) Bytes() ([]byte, error) { + return json.Marshal(cr) +} + +type claimInitiatorView struct { + issuer view.Identity + recipient view.Identity + pledgeInfo *Info + pledgeProof []byte +} + +func RequestClaim(context view.Context, issuer view.Identity, pledgeInfo *Info, recipient view.Identity, pledgeProof []byte) (view.Session, error) { + boxed, err := context.RunView(&claimInitiatorView{ + issuer: issuer, + pledgeInfo: pledgeInfo, + recipient: recipient, + pledgeProof: pledgeProof, + }) + if err != nil { + return nil, err + } + return boxed.(view.Session), err +} + +func (v *claimInitiatorView) Call(context view.Context) (interface{}, error) { + session, err := context.GetSession(context.Initiator(), v.issuer) + if err != nil { + return nil, err + } + + w := token.GetManagementService(context).WalletManager().OwnerWallet(v.recipient) + if w == nil { + return nil, errors.Wrapf(err, "cannot find owner wallet for recipient [%s]", v.recipient) + } + auditInfo, err := w.GetAuditInfo(v.recipient) + if err != nil { + return nil, errors.Wrapf(err, "failed getting audit info for recipient [%s]", v.recipient) + } + + req := &ClaimRequest{ + TokenType: v.pledgeInfo.TokenType, + Quantity: v.pledgeInfo.Amount, + Recipient: v.recipient, + RecipientAuditInfo: auditInfo, + ClaimDeadline: v.pledgeInfo.Script.Deadline, + OriginTokenID: v.pledgeInfo.TokenID, + OriginNetwork: v.pledgeInfo.Source, + PledgeProof: v.pledgeProof, + } + reqRaw, err := req.Bytes() + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling claim request") + } + signer, err := token.GetManagementService(context).SigService().GetSigner(v.recipient) + if err != nil { + return nil, err + } + req.RequestorSignature, err = signer.Sign(append(reqRaw, context.Me().Bytes()...)) + if err != nil { + return nil, err + } + reqRaw, err = req.Bytes() + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling claim request") + } + err = session.Send(reqRaw) + if err != nil { + return nil, err + } + + return session, nil +} + +type receiveClaimRequestView struct{} + +func ReceiveClaimRequest(context view.Context) (*ClaimRequest, error) { + req, err := context.RunView(&receiveClaimRequestView{}) + if err != nil { + return nil, err + } + return req.(*ClaimRequest), nil +} + +func (v *receiveClaimRequestView) Call(context view.Context) (interface{}, error) { + s := session.JSON(context) + req := &ClaimRequest{} + if err := s.Receive(&req); err != nil { + return nil, errors.Wrapf(err, "failed unmarshalling claim request") + } + tms := token.GetManagementService(context) + verifier, err := tms.SigService().OwnerVerifier(req.Recipient) + if err != nil { + return nil, errors.Wrapf(err, "failed to retrieve a verifier to check sender signature [%s]", req.Recipient) + } + request := &ClaimRequest{ + TokenType: req.TokenType, + Quantity: req.Quantity, + Recipient: req.Recipient, + RecipientAuditInfo: req.RecipientAuditInfo, + ClaimDeadline: req.ClaimDeadline, + OriginTokenID: req.OriginTokenID, + OriginNetwork: req.OriginNetwork, + PledgeProof: req.PledgeProof, + } + toBeVerified, err := json.Marshal(request) + if err != nil { + return nil, err + } + err = verifier.Verify(append(toBeVerified, s.Session().Info().Caller.Bytes()...), req.RequestorSignature) + if err != nil { + return nil, errors.Wrapf(err, "failed to verify claim request signature") + } + + if err := view2.GetEndpointService(context).Bind( + s.Session().Info().Caller, + req.Recipient, + ); err != nil { + return nil, errors.Wrapf(err, "failed binding caller's identity to request's recipient") + } + + if err := tms.WalletManager().RegisterRecipientIdentity(&token.RecipientData{ + Identity: req.Recipient, + AuditInfo: req.RecipientAuditInfo}); err != nil { + return nil, errors.Wrapf(err, "failed registering request recipient info") + } + + return req, nil +} + +func ValidateClaimRequest(context view.Context, req *ClaimRequest, opts ...ttx.TxOption) error { + txOpts, err := ttx.CompileTXOptions(opts...) + if err != nil { + return errors.WithMessage(err, "failed compiling tx options") + } + tms := token.GetManagementService(context, token.WithTMSID(txOpts.TMSID)) + if tms == nil { + return errors.Errorf("cannot find tms for [%s]", txOpts.TMSID) + } + + tmsID := tms.ID() + net := network.GetInstance(context, tmsID.Network, tmsID.Channel) + if net == nil { + return errors.Errorf("cannot find network for [%s]", tmsID) + } + destination := net.InteropURL(tmsID.Namespace) + + info := &Info{ + Amount: req.Quantity, + TokenID: req.OriginTokenID, + TokenMetadata: nil, + TokenType: req.TokenType, + Source: req.OriginNetwork, + Script: &Script{ + Deadline: req.ClaimDeadline, + Recipient: req.Recipient, + DestinationNetwork: destination, + }, + } + + if err := Vault(context).Store(info); err != nil { + return errors.WithMessagef(err, "failed storing temporary pledge info for [%s]", info.Source) + } + ssp, err := state.GetServiceProvider(context) + if err != nil { + return errors.WithMessage(err, "failed getting state service provider") + } + v, err := ssp.Verifier(info.Source) + if err != nil { + return errors.WithMessagef(err, "failed getting verifier for [%s]", info.Source) + } + // todo check that address in proof matches the source network + // todo check that destination network matches issuer's network + err = v.VerifyProofExistence(req.PledgeProof, req.OriginTokenID, info.TokenMetadata) + if err != nil { + logger.Errorf("proof of existence in claim request is not valid valid [%s]", err) + return errors.WithMessagef(err, "failed verifying proof of existence for [%s]", info.Source) + } + logger.Debugf("proof of existence in claim request is valid [%s]", err) + + if !req.ClaimDeadline.After(time.Now()) { + return errors.Errorf("deadline for claim has elapsed") + } + + return nil +} diff --git a/token/services/interop/pledge/deserializer.go b/token/services/interop/pledge/deserializer.go new file mode 100644 index 000000000..ae079719f --- /dev/null +++ b/token/services/interop/pledge/deserializer.go @@ -0,0 +1,165 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + "runtime/debug" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/deserializer" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/driver" + "github.com/pkg/errors" +) + +// ScriptInfo includes info about the sender and the recipient +type ScriptInfo struct { + Sender []byte + Recipient []byte +} + +func (si *ScriptInfo) Marshal() ([]byte, error) { + return json.Marshal(si) +} + +func (si *ScriptInfo) Unmarshal(raw []byte) error { + return json.Unmarshal(raw, si) +} + +type VerifierDES interface { + DeserializeVerifier(id token.Identity) (token.Verifier, error) +} + +type TypedIdentityDeserializer struct { + VerifierDeserializer VerifierDES +} + +func NewTypedIdentityDeserializer(verifierDeserializer VerifierDES) *TypedIdentityDeserializer { + return &TypedIdentityDeserializer{VerifierDeserializer: verifierDeserializer} +} + +func (t *TypedIdentityDeserializer) DeserializeVerifier(typ string, raw []byte) (token.Verifier, error) { + if typ != ScriptType { + return nil, errors.Errorf("cannot deserializer type [%s], expected [%s]", typ, ScriptType) + } + + script := &Script{} + err := json.Unmarshal(raw, script) + if err != nil { + return nil, errors.Errorf("failed to unmarshal TypedIdentity as an htlc script") + } + v := &Verifier{} + v.Sender, err = t.VerifierDeserializer.DeserializeVerifier(script.Sender) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal the identity of the sender [%v]", script.Sender.String()) + } + v.Issuer, err = t.VerifierDeserializer.DeserializeVerifier(script.Issuer) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal the identity of the issuer [%s]", script.Issuer.String()) + } + v.PledgeID = script.ID + return v, nil +} + +func (t *TypedIdentityDeserializer) Recipients(id token.Identity, typ string, raw []byte) ([]token.Identity, error) { + logger.Debugf("pledge, get recipients for [%s][%s]", id, typ) + if typ != ScriptType { + return nil, errors.New("unknown identity type") + } + + script := &Script{} + err := json.Unmarshal(raw, script) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal htlc script") + } + return []token.Identity{script.Issuer}, nil +} + +func (t *TypedIdentityDeserializer) GetOwnerAuditInfo(id token.Identity, typ string, raw []byte, p deserializer.AuditInfoProvider) ([][]byte, error) { + logger.Debugf("1. pledge, get owner audit info for [%s][%s]", id, typ) + if typ != ScriptType { + return nil, errors.Errorf("invalid type, got [%s], expected [%s]", typ, ScriptType) + } + script := &Script{} + var err error + err = json.Unmarshal(raw, script) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal htlc script") + } + + logger.Debugf("2. pledge, get owner audit info for [%s][%s]", id, typ) + auditInfo := &ScriptInfo{} + auditInfo.Sender, err = p.GetAuditInfo(script.Sender) + if err != nil { + return nil, errors.Wrapf(err, "failed getting audit info for sender of pledge script [%s]", view.Identity(raw).String()) + } + + logger.Debugf("3. pledge, get owner audit info for [%s][%s]", id, typ) + if len(auditInfo.Sender) == 0 { // in case this is a redeem we need to check the script issuer (and not the script sender) + auditInfo.Sender, err = p.GetAuditInfo(script.Issuer) + if err != nil { + return nil, errors.Wrapf(err, "failed getting audit info for issuer of pledge script [%s]", view.Identity(raw).String()) + } + if len(auditInfo.Sender) == 0 { + return nil, errors.Errorf("failed getting audit info for pledge script [%s]", view.Identity(raw).String()) + } + } + + logger.Debugf("4. pledge, get owner audit info for [%s][%s]", id, typ) + // Notice that recipient is in another network, but the issuer is + // the actual recipient of the script because it is in the same network. + auditInfo.Recipient, err = p.GetAuditInfo(script.Issuer) + if err != nil { + return nil, errors.Wrapf(err, "failed getting audit info for issuer of pledge script [%s]", view.Identity(raw).String()) + } + + logger.Debugf("5. pledge, get owner audit info for [%s][%s] [%s]", id, typ, debug.Stack()) + auditInfoRaw, err := json.Marshal(auditInfo) + if err != nil { + return nil, errors.Wrapf(err, "failed marshaling audit info for script") + } + return [][]byte{auditInfoRaw}, nil +} + +type AuditDeserializer struct { + AuditInfoDeserializer driver.AuditInfoDeserializer +} + +func NewAuditDeserializer(auditInfoDeserializer driver.AuditInfoDeserializer) *AuditDeserializer { + return &AuditDeserializer{AuditInfoDeserializer: auditInfoDeserializer} +} + +func (a *AuditDeserializer) DeserializeAuditInfo(bytes []byte) (driver.AuditInfo, error) { + si := &ScriptInfo{} + err := json.Unmarshal(bytes, si) + if err != nil || (len(si.Sender) == 0 && len(si.Recipient) == 0) { + return nil, errors.Errorf("ivalid audit info, failed unmarshal [%s][%d][%d]", string(bytes), len(si.Sender), len(si.Recipient)) + } + if len(si.Recipient) == 0 { + return nil, errors.Errorf("no recipient defined") + } + ai, err := a.AuditInfoDeserializer.DeserializeAuditInfo(si.Recipient) + if err != nil { + return nil, errors.Wrapf(err, "failed unamrshalling audit info [%s]", bytes) + } + return ai, nil +} + +// GetScriptSenderAndRecipient returns the script's sender, recipient, and issuer +func GetScriptSenderAndRecipient(ro *identity.TypedIdentity) (sender, recipient, issuer token.Identity, err error) { + if ro.Type == ScriptType { + script := &Script{} + err = json.Unmarshal(ro.Identity, script) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "failed to unmarshal htlc script") + } + return script.Sender, script.Recipient, script.Issuer, nil + } + return nil, nil, nil, errors.New("unknown identity type") +} diff --git a/token/services/interop/pledge/distribute.go b/token/services/interop/pledge/distribute.go new file mode 100644 index 000000000..0bb55507e --- /dev/null +++ b/token/services/interop/pledge/distribute.go @@ -0,0 +1,158 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/session" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/network" + token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" +) + +type Info struct { + // Source is the url of the network where the pledge is supposed to be + Source string + TokenType string + Amount uint64 + // TokenID is the ID of the token. + TokenID *token2.ID + TokenMetadata []byte + Script *Script +} + +func (i *Info) Bytes() ([]byte, error) { + return json.Marshal(i) +} + +func (i *Info) FromBytes(raw []byte) error { + return json.Unmarshal(raw, i) +} + +type DistributePledgeView struct { + tx *Transaction +} + +func NewDistributePledgeInfoView(tx *Transaction) *DistributePledgeView { + return &DistributePledgeView{ + tx: tx, + } +} + +func (v *DistributePledgeView) Call(context view.Context) (interface{}, error) { + outputs, err := v.tx.Outputs() + if err != nil { + return nil, errors.WithMessagef(err, "failed getting outputs") + } + if outputs.Count() < 1 { + return nil, errors.WithMessagef(err, "expected at least one output, got [%d]", outputs.Count()) + } + inputs, err := v.tx.TokenRequest.Inputs() + if err != nil { + return nil, errors.WithMessagef(err, "failed getting inputs") + } + if inputs.Count() < 1 { + return nil, errors.WithMessagef(err, "expected at least one input, got [%d]", inputs.Count()) + } + + var ret []*Info + for i := 0; i < outputs.Count(); i++ { + script := outputs.ScriptAt(i) + if script == nil { + continue + } + output := outputs.At(i) + + tokenID := &token2.ID{ + TxId: v.tx.ID(), + Index: uint64(i), + } + // TODO: retrieve token's metadata + + tmsID := v.tx.TokenService().ID() + net := network.GetInstance(context, tmsID.Network, tmsID.Channel) + if net == nil { + return nil, errors.Errorf("cannot find network for [%s]", tmsID) + } + info := &Info{ + Source: net.InteropURL(tmsID.Namespace), + TokenType: output.Type, + Amount: output.Quantity.ToBigInt().Uint64(), + TokenID: tokenID, + TokenMetadata: nil, + Script: script, + } + + session, err := context.GetSession(context.Initiator(), script.Recipient) + if err != nil { + return nil, err + } + infoRaw, err := info.Bytes() + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling pledge info") + } + err = session.Send(infoRaw) + if err != nil { + return nil, err + } + + // Wait for a signed ack, but who should sign? What if recipient is an identity that this node does + // not recognize? + ret = append(ret, info) + } + + return ret, nil +} + +type pledgeReceiverView struct{} + +func ReceivePledgeInfo(context view.Context) (*Info, error) { + info, err := context.RunView(&pledgeReceiverView{}) + if err != nil { + return nil, err + } + return info.(*Info), nil +} + +func (v *pledgeReceiverView) Call(context view.Context) (interface{}, error) { + _, payload, err := session.ReadFirstMessage(context) + if err != nil { + return nil, err + } + info := &Info{} + if err := info.FromBytes(payload); err != nil { + return nil, errors.Wrapf(err, "failed unmarshalling pledge info") + } + + return info, nil +} + +type AcceptPledgeInfoView struct { + info *Info +} + +func NewAcceptPledgeInfoView(info *Info) *AcceptPledgeInfoView { + return &AcceptPledgeInfoView{ + info: info, + } +} + +func (a *AcceptPledgeInfoView) Call(context view.Context) (interface{}, error) { + // Store info + if err := Vault(context).Store(a.info); err != nil { + return nil, errors.Wrapf(err, "failed storing pledge info") + } + + // raw, err := a.info.Bytes() + // if err != nil { + // return nil, errors.Wrapf(err, "failed marshalling info to raw") + // } + + return nil, nil +} diff --git a/token/services/interop/pledge/endorsement.go b/token/services/interop/pledge/endorsement.go new file mode 100644 index 000000000..2ee411170 --- /dev/null +++ b/token/services/interop/pledge/endorsement.go @@ -0,0 +1,65 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "time" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" + "github.com/pkg/errors" +) + +// NewCollectEndorsementsView returns an instance of the ttx collectEndorsementsView struct +func NewCollectEndorsementsView(tx *Transaction) view.View { + return ttx.NewCollectEndorsementsView(tx.Transaction) +} + +type ReceiveTransactionView struct { +} + +// NewReceiveTransactionView returns an instance of receiveTransactionView struct +func NewReceiveTransactionView() *ReceiveTransactionView { + return &ReceiveTransactionView{} +} + +func (f *ReceiveTransactionView) Call(context view.Context) (interface{}, error) { + // Wait to receive a transaction back + ch := context.Session().Receive() + + select { + case msg := <-ch: + if msg.Status == view.ERROR { + return nil, errors.New(string(msg.Payload)) + } + tx, err := NewTransactionFromBytes(context, msg.Payload) + if err != nil { + return nil, err + } + return tx, nil + case <-time.After(240 * time.Second): + return nil, errors.New("timeout reached") + } +} + +// ReceiveTransaction executes the receiveTransactionView and returns the received transaction +func ReceiveTransaction(context view.Context) (*Transaction, error) { + logger.Debugf("receive a new transaction...") + + txBoxed, err := context.RunView(NewReceiveTransactionView()) + if err != nil { + return nil, err + } + + cctx, ok := txBoxed.(*Transaction) + if !ok { + return nil, errors.Errorf("received transaction of wrong type [%T]", cctx) + } + logger.Debugf("received transaction with id [%s]", cctx.ID()) + + return cctx, nil +} diff --git a/token/services/interop/pledge/finality.go b/token/services/interop/pledge/finality.go new file mode 100644 index 000000000..eb340d73c --- /dev/null +++ b/token/services/interop/pledge/finality.go @@ -0,0 +1,17 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" +) + +// NewFinalityView returns an instance of the ttx FinalityView +func NewFinalityView(tx *Transaction) view.View { + return ttx.NewFinalityView(tx.Transaction) +} diff --git a/token/services/interop/pledge/logger.go b/token/services/interop/pledge/logger.go new file mode 100644 index 000000000..2b7d8287f --- /dev/null +++ b/token/services/interop/pledge/logger.go @@ -0,0 +1,11 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/flogging" + +var logger = flogging.MustGetLogger("token-sdk.services.pledge") diff --git a/token/services/interop/pledge/ordering.go b/token/services/interop/pledge/ordering.go new file mode 100644 index 000000000..c3bf2f2a9 --- /dev/null +++ b/token/services/interop/pledge/ordering.go @@ -0,0 +1,17 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" +) + +// NewOrderingAndFinalityView returns a new instance of the ttx orderingAndFinalityView struct +func NewOrderingAndFinalityView(tx *Transaction) view.View { + return ttx.NewOrderingAndFinalityView(tx.Transaction) +} diff --git a/token/services/interop/pledge/pledge.go b/token/services/interop/pledge/pledge.go new file mode 100644 index 000000000..e1435d4b1 --- /dev/null +++ b/token/services/interop/pledge/pledge.go @@ -0,0 +1,100 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "crypto/rand" + "encoding/hex" + "encoding/json" + "time" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" + "github.com/pkg/errors" +) + +const ( + MetadataKey = "metadata.pledge" + defaultDeadlineOffset = time.Hour +) + +func (t *Transaction) Pledge(wallet *token.OwnerWallet, destNetwork string, deadline time.Duration, recipient view.Identity, issuer view.Identity, typ string, value uint64) (string, error) { + if deadline == 0 { + deadline = defaultDeadlineOffset + } + if destNetwork == "" { + return "", errors.Errorf("must specify a destination network") + } + if issuer.IsNone() { + return "", errors.Errorf("must specify an issuer") + } + if recipient.IsNone() { + return "", errors.Errorf("must specify a recipient") + } + pledgeID, err := generatePledgeID() + if err != nil { + return "", errors.Wrapf(err, "failed to generate pledge ID") + } + me, err := wallet.GetRecipientIdentity() + if err != nil { + return "", err + } + script, err := t.recipientAsScript(me, destNetwork, deadline, recipient, issuer, pledgeID) + if err != nil { + return "", err + } + _, err = t.TokenRequest.Transfer( + t.Context, + wallet, + typ, + []uint64{value}, + []view.Identity{script}, + token.WithTransferMetadata(MetadataKey+pledgeID, []byte("1")), + ) + return pledgeID, err +} + +func (t *Transaction) recipientAsScript(sender view.Identity, destNetwork string, deadline time.Duration, recipient view.Identity, issuer view.Identity, pledgeID string) (view.Identity, error) { + script := Script{ + Deadline: time.Now().Add(deadline), + DestinationNetwork: destNetwork, + Recipient: recipient, + Issuer: issuer, + Sender: sender, + ID: pledgeID, + } + rawScript, err := json.Marshal(script) + if err != nil { + return nil, err + } + + ro := &identity.TypedIdentity{ + Type: ScriptType, + Identity: rawScript, + } + return ro.Bytes() +} + +// generatePledgeID generates a pledgeID randomly +func generatePledgeID() (string, error) { + nonce, err := getRandomNonce() + if err != nil { + return "", errors.New("failed generating random nonce for pledgeID") + } + return hex.EncodeToString(nonce), nil +} + +// getRandomNonce generates a random nonce using the package math/rand +func getRandomNonce() ([]byte, error) { + key := make([]byte, 24) + _, err := rand.Read(key) + if err != nil { + return nil, errors.Wrap(err, "error getting random bytes") + } + return key, nil +} diff --git a/token/services/interop/pledge/recipients.go b/token/services/interop/pledge/recipients.go new file mode 100644 index 000000000..f0dec0c32 --- /dev/null +++ b/token/services/interop/pledge/recipients.go @@ -0,0 +1,179 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view" + session2 "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/session" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" + "github.com/pkg/errors" +) + +type RecipientData = token.RecipientData + +type RecipientRequest struct { + NetworkURL string + WalletID []byte +} + +type RequestRecipientIdentityView struct { + TMSID token.TMSID + DestNetwork string + Other view.Identity +} + +// RequestPledgeRecipientIdentity executes the RequestRecipientIdentityView. +// The sender contacts the recipient's FSC node identified via the passed view identity. +// The sender gets back the identity the recipient wants to use to assign ownership of tokens. +func RequestPledgeRecipientIdentity(context view.Context, recipient view.Identity, destNetwork string, opts ...token.ServiceOption) (view.Identity, error) { + options, err := token.CompileServiceOptions(opts...) + if err != nil { + return nil, err + } + pseudonymBoxed, err := context.RunView(&RequestRecipientIdentityView{ + TMSID: options.TMSID(), + DestNetwork: destNetwork, + Other: recipient, + }) + if err != nil { + return nil, err + } + return pseudonymBoxed.(view.Identity), nil +} + +func (f RequestRecipientIdentityView) Call(context view.Context) (interface{}, error) { + logger.Debugf("request recipient to [%s] for TMS [%s]", f.Other, f.TMSID) + + tms := token.GetManagementService(context, token.WithTMSID(f.TMSID)) + + if w := tms.WalletManager().OwnerWallet(f.Other); w != nil { + recipient, err := w.GetRecipientIdentity() + if err != nil { + return nil, err + } + return recipient, nil + } else { + session, err := session2.NewJSON(context, context.Initiator(), f.Other) + if err != nil { + return nil, err + } + + // Ask for identity + err = session.Send(&RecipientRequest{ + NetworkURL: f.DestNetwork, + WalletID: f.Other, + }) + if err != nil { + return nil, errors.Wrapf(err, "failed to send recipient request") + } + + // Wait to receive a view identity + recipientData := &RecipientData{} + if err := session.Receive(recipientData); err != nil { + return nil, errors.Wrapf(err, "failed to receive recipient data") + } + //if err := tms.WalletManager().RegisterRecipientIdentity(recipientData); err != nil { + // return nil, err + //} + + // Update the Endpoint Resolver + if err := view2.GetEndpointService(context).Bind(f.Other, recipientData.Identity); err != nil { + return nil, err + } + + return recipientData.Identity, nil + } +} + +type RespondRequestPledgeRecipientIdentityView struct { + Wallet string +} + +// RespondRequestPledgeRecipientIdentity executes the RespondRequestPledgeRecipientIdentityView. +// The recipient sends back the identity to receive ownership of tokens. +// The identity is taken from the wallet +func RespondRequestPledgeRecipientIdentity(context view.Context) (view.Identity, error) { + id, err := context.RunView(&RespondRequestPledgeRecipientIdentityView{}) + if err != nil { + return nil, err + } + return id.(view.Identity), nil +} + +func (s *RespondRequestPledgeRecipientIdentityView) Call(context view.Context) (interface{}, error) { + session := session2.JSON(context) + recipientRequest := &RecipientRequest{} + if err := session.Receive(recipientRequest); err != nil { + return nil, errors.Wrapf(err, "failed unmarshalling recipient request") + } + + wallet := s.Wallet + if len(wallet) == 0 && len(recipientRequest.WalletID) != 0 { + wallet = string(recipientRequest.WalletID) + } + + ssp, err := state.GetServiceProvider(context) + if err != nil { + return nil, errors.Errorf("failed to load state service provider") + } + tmsID, err := ssp.URLToTMSID(recipientRequest.NetworkURL) + if err != nil { + return nil, errors.Wrapf(err, "failed parsing destination [%s]", recipientRequest.NetworkURL) + } + w := GetWallet( + context, + wallet, + token.WithTMSID(tmsID), + ) + if w == nil { + return nil, errors.Errorf("unable to get wallet %s in %s", wallet, tmsID) + } + recipientIdentity, err := w.GetRecipientIdentity() + if err != nil { + return nil, errors.Wrapf(err, "failed getting recipient identity for wallet [%s]", wallet) + } + auditInfo, err := w.GetAuditInfo(recipientIdentity) + if err != nil { + return nil, errors.Wrapf(err, "failed getting audit info for recipient [%s], wallet [%s]", recipientIdentity, wallet) + } + metadata, err := w.GetTokenMetadata(recipientIdentity) + if err != nil { + return nil, errors.Wrapf(err, "failed getting token metadata for recipient [%s], wallet [%s]", recipientIdentity, wallet) + } + + // Step 3: send the public key back to the invoker + err = session.Send(&RecipientData{ + Identity: recipientIdentity, + AuditInfo: auditInfo, + TokenMetadata: metadata, + }) + if err != nil { + return nil, err + } + + // Update the Endpoint Resolver + resolver := view2.GetEndpointService(context) + err = resolver.Bind(context.Me(), recipientIdentity) + if err != nil { + return nil, err + } + + return recipientIdentity, nil +} + +// RequestRecipientIdentity executes the RequestRecipientIdentityView. +func RequestRecipientIdentity(context view.Context, recipient view.Identity, opts ...token.ServiceOption) (view.Identity, error) { + return ttx.RequestRecipientIdentity(context, recipient, opts...) +} + +// RespondRequestRecipientIdentity executes the RespondRequestRecipientIdentityView. +func RespondRequestRecipientIdentity(context view.Context) (view.Identity, error) { + return ttx.RespondRequestRecipientIdentity(context) +} diff --git a/token/services/interop/pledge/reclaim.go b/token/services/interop/pledge/reclaim.go new file mode 100644 index 000000000..5f88748ed --- /dev/null +++ b/token/services/interop/pledge/reclaim.go @@ -0,0 +1,98 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + "fmt" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" + token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" +) + +const ( + MetadataReclaimKey = "metadata.reclaim" +) + +func (t *Transaction) Reclaim(wallet *token.OwnerWallet, tok *token2.UnspentToken, issuerSignature []byte, tokenID *token2.ID, proof []byte) error { + if proof == nil { + return errors.New("must provide proof") + } + if tokenID == nil { + return errors.New("must provide token ID") + } + + q, err := token2.ToQuantity(tok.Quantity, t.TokenRequest.TokenService.PublicParametersManager().PublicParameters().Precision()) + if err != nil { + return errors.Wrapf(err, "failed to convert quantity [%s]", tok.Quantity) + } + + owner, err := identity.UnmarshalTypedIdentity(tok.Owner.Raw) + if err != nil { + return err + } + + if owner.Type != ScriptType { + return errors.Errorf("invalid owner type, expected a pledge script") + } + + script := &Script{} + err = json.Unmarshal(owner.Identity, script) + if err != nil { + return errors.Errorf("failed to unmarshal TypedIdentity as a pledge script") + } + + // Register the signer for the reclaim + sigService := t.TokenService().SigService() + signer, err := sigService.GetSigner(script.Sender) + if err != nil { + return err + } + verifier, err := sigService.OwnerVerifier(script.Sender) + if err != nil { + return err + } + // TODO: script.Issues is an owner identity, shall we switch to issuer identity? + issuer, err := sigService.OwnerVerifier(script.Issuer) + if err != nil { + return err + } + reclaimSigner := &Signer{Sender: signer, IssuerSignature: issuerSignature} + reclaimVerifier := &Verifier{ + Sender: verifier, + Issuer: issuer, + PledgeID: script.ID, + } + logger.Debugf("registering signer for reclaim...") + if err := sigService.RegisterSigner( + tok.Owner.Raw, + reclaimSigner, + reclaimVerifier, + ); err != nil { + return err + } + + if err := t.Binder.Bind(script.Sender, tok.Owner.Raw); err != nil { + return err + } + + proofKey := MetadataReclaimKey + fmt.Sprintf(".%d.%s", tokenID.Index, tokenID.TxId) + + _, err = t.TokenRequest.Transfer( + t.Context, + wallet, + tok.Type, + []uint64{q.ToBigInt().Uint64()}, + []view.Identity{script.Sender}, + token.WithTokenIDs(tok.Id), + token.WithTransferMetadata(proofKey, proof), + ) + return err +} diff --git a/token/services/interop/pledge/redeem.go b/token/services/interop/pledge/redeem.go new file mode 100644 index 000000000..63e28d74f --- /dev/null +++ b/token/services/interop/pledge/redeem.go @@ -0,0 +1,94 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + "fmt" + + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" + token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" +) + +const ( + RedeemPledgeKey = "metadata.redeemPledge" +) + +// RedeemPledge appends a redeem action to the request. The action will be prepared using the provided owner wallet. +// The action redeems the passed token. +func (t *Transaction) RedeemPledge(wallet *token.OwnerWallet, tok *token2.UnspentToken, tokenID *token2.ID, proof []byte) error { + if proof == nil { + return errors.New("must provide proof") + } + if tokenID == nil { + return errors.New("must provide token ID") + } + + q, err := token2.ToQuantity(tok.Quantity, t.TokenRequest.TokenService.PublicParametersManager().PublicParameters().Precision()) + if err != nil { + return errors.Wrapf(err, "failed to convert quantity [%s]", tok.Quantity) + } + + owner, err := identity.UnmarshalTypedIdentity(tok.Owner.Raw) + if err != nil { + return err + } + script := &Script{} + if owner.Type != ScriptType { + return errors.Errorf("invalid owner type, expected a pledge script") + } + + err = json.Unmarshal(owner.Identity, script) + if err != nil { + return errors.Errorf("failed to unmarshal TypedIdentity as a pledge script") + } + + // Register the signer for the redeem + sigService := t.TokenService().SigService() + signer, err := sigService.GetSigner(script.Issuer) + if err != nil { + return err + } + verifier, err := sigService.OwnerVerifier(script.Issuer) + if err != nil { + return err + } + // TODO: script.Issues is an owner identity, shall we switch to issuer identity? + if err != nil { + return err + } + redeemSigner := &Signer{Issuer: signer} + redeemVerifier := &Verifier{ + Issuer: verifier, + } + logger.Debugf("registering signer for redeem...") + if err := sigService.RegisterSigner( + tok.Owner.Raw, + redeemSigner, + redeemVerifier, + ); err != nil { + return err + } + + if err := t.Binder.Bind(script.Issuer, tok.Owner.Raw); err != nil { + return err + } + + proofKey := RedeemPledgeKey + fmt.Sprintf(".%d.%s", tokenID.Index, tokenID.TxId) + + err = t.TokenRequest.Redeem( + t.Context, + wallet, + tok.Type, + q.ToBigInt().Uint64(), + token.WithTokenIDs(tok.Id), + token.WithTransferMetadata(proofKey, proof), + ) + return err +} diff --git a/token/services/interop/pledge/scanner.go b/token/services/interop/pledge/scanner.go new file mode 100644 index 000000000..ac2ebd797 --- /dev/null +++ b/token/services/interop/pledge/scanner.go @@ -0,0 +1,62 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "time" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/network" + "github.com/pkg/errors" +) + +const ( + ScanForPledgeIDStartingTransaction = "pledge.IDExists.StartingTransaction" +) + +// WithStartingTransaction sets the starting transaction for the scan +func WithStartingTransaction(txID string) token.ServiceOption { + return func(o *token.ServiceOptions) error { + if o.Params == nil { + o.Params = map[string]interface{}{} + } + o.Params[ScanForPledgeIDStartingTransaction] = txID + return nil + } +} + +// IDExists scans the ledger for a pledge identifier, taking into account the timeout +// IDExists returns true, if entry identified by key (MetadataKey+pledgeID) is occupied. +func IDExists(ctx view.Context, pledgeID string, timeout time.Duration, opts ...token.ServiceOption) (bool, error) { + logger.Debugf("scanning for pledgeID of [%s] with timeout [%s]", pledgeID, timeout) + tokenOptions, err := token.CompileServiceOptions(opts...) + if err != nil { + return false, err + } + tms := token.GetManagementService(ctx, opts...) + + net := network.GetInstance(ctx, tms.Network(), tms.Channel()) + if net == nil { + return false, errors.Errorf("cannot find network [%s:%s]", tms.Namespace(), tms.Channel()) + } + + startingTxID, err := tokenOptions.ParamAsString(ScanForPledgeIDStartingTransaction) + if err != nil { + return false, errors.Wrapf(err, "invalid starting transaction param") + } + + pledgeKey := MetadataKey + pledgeID + v, err := net.LookupTransferMetadataKey(tms.Namespace(), startingTxID, pledgeKey, timeout, false, opts...) + if err != nil { + return false, errors.Wrapf(err, "failed to lookup transfer metadata for pledge ID [%s]", pledgeID) + } + if len(v) != 0 { + return true, nil + } + return false, nil +} diff --git a/token/services/interop/pledge/script.go b/token/services/interop/pledge/script.go new file mode 100644 index 000000000..2746517dd --- /dev/null +++ b/token/services/interop/pledge/script.go @@ -0,0 +1,55 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "time" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/pkg/errors" +) + +const ScriptType = "pledge" // pledge script + +type Script struct { + Sender view.Identity + Recipient view.Identity + DestinationNetwork string + Deadline time.Time + Issuer view.Identity + ID string +} + +// Validate checks that all fields of pledge script are correctly set +func (s *Script) Validate(timeReference time.Time) error { + if err := s.WellFormedness(); err != nil { + return err + } + if s.Deadline.Before(timeReference) { + return errors.New("invalid pledge script: deadline already elapsed") + } + return nil +} + +func (s *Script) WellFormedness() error { + if s.Sender.IsNone() { + return errors.New("invalid pledge script: empty sender") + } + if s.Recipient.IsNone() { + return errors.New("invalid pledge script: empty recipient") + } + if s.DestinationNetwork == "" { + return errors.New("invalid pledge script: empty destination network") + } + if s.Issuer.IsNone() { + return errors.New("invalid pledge script: empty issuer") + } + if s.ID == "" { + return errors.New("invalid pledge script: empty identifier") + } + return nil +} diff --git a/token/services/interop/pledge/signer.go b/token/services/interop/pledge/signer.go new file mode 100644 index 000000000..35497bac8 --- /dev/null +++ b/token/services/interop/pledge/signer.go @@ -0,0 +1,105 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/hash" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/pkg/errors" +) + +// Signer for a pledge script +type Signer struct { + // Sender corresponds to the sender of the token at time of pledge + // this is used during reclaim + Sender token.Signer + // Issuer corresponds to the issuer in the origin network + // this is used during redeem + Issuer token.Signer + // IssuerSignature is the signature from the issuer in the origin network + // it attests to whether a pledged token has been successfully claimed or not + // this is used during reclaim + IssuerSignature []byte +} + +// Signature encodes the signature that spends a pledge script +type Signature struct { + Reclaim bool + // this is empty in case of redeem + SenderSignature []byte + IssuerSignature []byte +} + +func (s *Signer) Sign(message []byte) ([]byte, error) { + sigma := Signature{} + var err error + if s.Issuer == nil { + if s.Sender == nil { + return nil, errors.New("please initialize pledge signer correctly: empty sender") + } + sigma.Reclaim = true + sigma.IssuerSignature = s.IssuerSignature + + message = append(message, s.IssuerSignature...) + sigma.SenderSignature, err = s.Sender.Sign(message) + if err != nil { + return nil, err + } + logger.Debugf("reclaim signature on message [%s]", hash.Hashable(message).String()) + } else { + sigma.Reclaim = false + sigma.IssuerSignature, err = s.Issuer.Sign(message) + if err != nil { + return nil, err + } + logger.Debugf("redeem signature on message [%s]", hash.Hashable(message).String()) + } + raw, err := json.Marshal(sigma) + if err != nil { + return nil, err + } + return raw, nil +} + +// Verifier of a Signature it is uniquely linked to a the pledge script identified by +// PledgeID +type Verifier struct { + // Sender in the pledge script + Sender token.Verifier + // Issuer is the issuer in the pledge script + Issuer token.Verifier + // PledgeID identifies the pledge script + PledgeID string +} + +// Verify checks if a signature is a valid signature on the message with respect to TransferVerifier +func (v *Verifier) Verify(message, sigma []byte) error { + sig := &Signature{} + err := json.Unmarshal(sigma, sig) + if err != nil { + return errors.Wrapf(err, "failed unmarshalling signature [%s] on message [%s]", hash.Hashable(sigma).String(), hash.Hashable(message).String()) + } + // this is a redeem + if !sig.Reclaim { + if err := v.Issuer.Verify(message, sig.IssuerSignature); err != nil { + return errors.Wrapf(err, "failed verifying signature [%s] on message [%s], this is not a valid redeem", hash.Hashable(sigma).String(), hash.Hashable(message).String()) + } + return nil + } + // this is a reclaim + message = append(message, sig.IssuerSignature...) + if err := v.Sender.Verify(message, sig.SenderSignature); err != nil { + return errors.Wrapf(err, "failed verifying signature [%s] on message [%s], this is not a valid reclaim", hash.Hashable(sigma).String(), hash.Hashable(message).String()) + } + err = v.Issuer.Verify([]byte(v.PledgeID), sig.IssuerSignature) + if err != nil { + return errors.Wrapf(err, "failed verifying reclaim issuer signature [%s:%s]", v.PledgeID, hash.Hashable(sig.IssuerSignature).String()) + } + return nil +} diff --git a/token/services/interop/pledge/state.go b/token/services/interop/pledge/state.go new file mode 100644 index 000000000..5f8e19c2f --- /dev/null +++ b/token/services/interop/pledge/state.go @@ -0,0 +1,207 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "time" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/network" + token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" +) + +type CollectProofOfExistenceView struct { + tokenID *token2.ID + source string +} + +func NewCollectProofOfExistenceView(tokenID *token2.ID, source string) *CollectProofOfExistenceView { + return &CollectProofOfExistenceView{ + tokenID: tokenID, + source: source, + } +} + +func (c *CollectProofOfExistenceView) Call(context view.Context) (interface{}, error) { + // get a query executor for the target network that should contain the pledge + ssp, err := state.GetServiceProvider(context) + if err != nil { + return nil, errors.WithMessage(err, "failed getting state service provider") + } + p, err := ssp.QueryExecutor(c.source) + if err != nil { + return nil, errors.WithMessagef(err, "failed getting prover for [%s]", c.source) + } + return p.Exist(c.tokenID) +} + +type CollectProofOfNonExistenceView struct { + origin string + tokenID *token2.ID + deadline time.Time + destination string +} + +func NewCollectProofOfNonExistenceView(tokenID *token2.ID, origin string, deadline time.Time, destination string) *CollectProofOfNonExistenceView { + return &CollectProofOfNonExistenceView{ + origin: origin, + tokenID: tokenID, + deadline: deadline, + destination: destination, + } +} + +func (c *CollectProofOfNonExistenceView) Call(context view.Context) (interface{}, error) { + ssp, err := state.GetServiceProvider(context) + if err != nil { + return nil, errors.WithMessage(err, "failed getting state service provider") + } + p, err := ssp.QueryExecutor(c.destination) + if err != nil { + return nil, errors.WithMessagef(err, "failed getting prover for [%s]", c.destination) + } + return p.DoesNotExist(c.tokenID, c.origin, c.deadline) +} + +type CollectProofOfTokenWithMetadataExistenceView struct { + origin string + tokenID *token2.ID + destination string +} + +func NewCollectProofOfTokenWithMetadataExistenceView(tokenID *token2.ID, origin string, destination string) *CollectProofOfTokenWithMetadataExistenceView { + return &CollectProofOfTokenWithMetadataExistenceView{ + origin: origin, + tokenID: tokenID, + destination: destination, + } +} + +func (c *CollectProofOfTokenWithMetadataExistenceView) Call(context view.Context) (interface{}, error) { + ssp, err := state.GetServiceProvider(context) + if err != nil { + return nil, errors.WithMessage(err, "failed getting state service provider") + } + p, err := ssp.QueryExecutor(c.destination) + if err != nil { + return nil, errors.WithMessagef(err, "failed getting prover for [%s]", c.destination) + } + return p.ExistsWithMetadata(c.tokenID, c.origin) +} + +// RequestProofOfExistence requests a proof of the existence of a pledge corresponding to the passed information +func RequestProofOfExistence(context view.Context, info *Info) ([]byte, error) { + // collect proof + boxed, err := context.RunView(NewCollectProofOfExistenceView(info.TokenID, info.Source)) + if err != nil { + return nil, err + } + proof, ok := boxed.([]byte) + if !ok { + return nil, errors.Errorf("failed to collect proof of existence") + } + + // verify proof before returning it + // get a proof verifier for the network that generated the proof + ssp, err := state.GetServiceProvider(context) + if err != nil { + return nil, errors.WithMessage(err, "failed getting state service provider") + } + v, err := ssp.Verifier(info.Source) + if err != nil { + return nil, errors.WithMessagef(err, "failed getting verifier for [%s]", info.Source) + } + if err := v.VerifyProofExistence(proof, info.TokenID, info.TokenMetadata); err != nil { + return nil, errors.WithMessagef(err, "failed verifying proof of existence for [%s]", info.Source) + } + + return proof, nil +} + +// RequestProofOfNonExistence request a proof of non-existence of the given token, originally created in the given network, +// in the destination network identified by the given script. +// If no error is returned, the proof is valid with the respect to the given script. +func RequestProofOfNonExistence(context view.Context, tokenID *token2.ID, originTMSID token.TMSID, script *Script) ([]byte, error) { + // collect proof + net := network.GetInstance(context, originTMSID.Network, originTMSID.Channel) + if net == nil { + return nil, errors.Errorf("cannot find network for [%s]", originTMSID) + } + originNetwork := net.InteropURL(originTMSID.Namespace) + + boxed, err := context.RunView(NewCollectProofOfNonExistenceView( + tokenID, + originNetwork, + script.Deadline, + script.DestinationNetwork, + )) + if err != nil { + return nil, err + } + proof, ok := boxed.([]byte) + if !ok { + return nil, errors.Errorf("failed to collect proof of non-existence") + } + + // verify proof before returning it + ssp, err := state.GetServiceProvider(context) + if err != nil { + return nil, errors.WithMessage(err, "failed getting state service provider") + } + v, err := ssp.Verifier(script.DestinationNetwork) + if err != nil { + return nil, errors.WithMessagef(err, "failed getting verifier for [%s]", script.DestinationNetwork) + } + if err := v.VerifyProofNonExistence(proof, tokenID, originNetwork, script.Deadline); err != nil { + return nil, errors.WithMessagef(err, "failed verifying proof of non-existence for [%s]", originNetwork) + } + + return proof, nil +} + +// RequestProofOfExistenceOfTokenWithMetadata request a proof of a token existence with the given token ID and origin network, +// in the destination network identified by the given script. +// If no error is returned, the proof is valid with the respect to the given script. +func RequestProofOfExistenceOfTokenWithMetadata(context view.Context, tokenID *token2.ID, originTMSID token.TMSID, script *Script) ([]byte, error) { + // collect proof + net := network.GetInstance(context, originTMSID.Network, originTMSID.Channel) + if net == nil { + return nil, errors.Errorf("cannot find network for [%s]", originTMSID) + } + originNetwork := net.InteropURL(originTMSID.Namespace) + + boxed, err := context.RunView(NewCollectProofOfTokenWithMetadataExistenceView( + tokenID, + originNetwork, + script.DestinationNetwork, + )) + if err != nil { + return nil, errors.WithMessagef(err, "failed collecting proof of token existence for [%s][%s]", originTMSID, tokenID) + } + proof, ok := boxed.([]byte) + if !ok { + return nil, errors.Errorf("failed to collect proof of token existence") + } + + // verify proof before returning it + ssp, err := state.GetServiceProvider(context) + if err != nil { + return nil, errors.WithMessage(err, "failed getting state service provider") + } + v, err := ssp.Verifier(script.DestinationNetwork) + if err != nil { + return nil, errors.WithMessagef(err, "failed getting verifier for [%s]", script.DestinationNetwork) + } + if err := v.VerifyProofTokenWithMetadataExistence(proof, tokenID, originNetwork); err != nil { + return nil, errors.WithMessagef(err, "failed verifying proof of token existence for [%s]", originNetwork) + } + + return proof, nil +} diff --git a/token/services/interop/pledge/store.go b/token/services/interop/pledge/store.go new file mode 100644 index 000000000..59a835d7c --- /dev/null +++ b/token/services/interop/pledge/store.go @@ -0,0 +1,107 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "github.com/hyperledger-labs/fabric-smart-client/platform/view" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/hash" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/kvs" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" +) + +type VaultStore struct { + store *kvs.KVS +} + +func NewVaultStore(store *kvs.KVS) *VaultStore { + return &VaultStore{store: store} +} + +func Vault(sf view.ServiceProvider) *VaultStore { + store, err := sf.GetService(&VaultStore{}) + if err != nil { + panic(err) + } + return store.(*VaultStore) +} + +func (ps *VaultStore) Store(info *Info) error { + raw, err := info.Bytes() + if err != nil { + return errors.Wrapf(err, "failed marshalling info to raw") + } + key, err := kvs.CreateCompositeKey( + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge", + []string{ + "pledge", + "info", + hash.Hashable(raw).String(), + }, + ) + if err != nil { + return errors.Wrapf(err, "failed creating key for info [%v]", info) + } + return ps.store.Put( + key, + info, + ) +} + +func (ps *VaultStore) PledgeByTokenID(tokenID *token.ID) (*Info, error) { + if tokenID == nil { + return nil, errors.Errorf("passed nil token id") + } + it, err := ps.store.GetByPartialCompositeID( + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge", + []string{ + "pledge", + "info", + }, + ) + if err != nil { + return nil, errors.Wrapf(err, "failed getting iterator over pledges") + } + + var res *Info + for it.HasNext() { + var info *Info + if _, err := it.Next(&info); err != nil { + return nil, errors.Wrapf(err, "failed getting next pledge info") + } + if info.TokenID.TxId == tokenID.TxId && info.TokenID.Index == tokenID.Index { + res = info + break + } + } + + return res, nil +} + +func (ps *VaultStore) Delete(pledges []*Info) error { + for _, info := range pledges { + raw, err := info.Bytes() + if err != nil { + return errors.Wrapf(err, "failed marshalling info to raw") + } + key, err := kvs.CreateCompositeKey( + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge", + []string{ + "pledge", + "info", + hash.Hashable(raw).String(), + }, + ) + if err != nil { + return errors.Wrapf(err, "failed creating key for info [%v]", info) + } + if err := ps.store.Delete(key); err != nil { + return errors.WithMessagef(err, "failed deleting [%s]", key) + } + } + return nil +} diff --git a/token/services/interop/pledge/stream.go b/token/services/interop/pledge/stream.go new file mode 100644 index 000000000..1e0fce1f3 --- /dev/null +++ b/token/services/interop/pledge/stream.go @@ -0,0 +1,71 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token" +) + +type OutputStream struct { + *token.OutputStream +} + +func NewOutputStream(outputs *token.OutputStream) *OutputStream { + return &OutputStream{OutputStream: outputs} +} + +func (o *OutputStream) Filter(f func(t *token.Output) bool) *OutputStream { + return NewOutputStream(o.OutputStream.Filter(f)) +} + +func (o *OutputStream) ByRecipient(id view.Identity) *OutputStream { + return o.Filter(func(t *token.Output) bool { + return id.Equal(t.Owner) + }) +} + +func (o *OutputStream) ByType(typ string) *OutputStream { + return o.Filter(func(t *token.Output) bool { + return t.Type == typ + }) +} + +func (o *OutputStream) ByScript() *OutputStream { + return o.Filter(func(t *token.Output) bool { + owner, err := identity.UnmarshalTypedIdentity(t.Owner) + if err != nil { + return false + } + return owner.Type == ScriptType + }) +} + +func (o *OutputStream) ScriptAt(i int) *Script { + tok := o.OutputStream.At(i) + owner, err := identity.UnmarshalTypedIdentity(tok.Owner) + if err != nil { + logger.Debugf("failed unmarshalling raw owner [%s]: [%s]", tok, err) + return nil + } + if owner.Type == ScriptType { + script := &Script{} + if err := json.Unmarshal(owner.Identity, script); err != nil { + logger.Debugf("failed unmarshalling pledge script [%s]: [%s]", tok, err) + return nil + } + if script.Sender.IsNone() || script.Recipient.IsNone() { + return nil + } + return script + } + return nil +} diff --git a/token/services/interop/pledge/transaction.go b/token/services/interop/pledge/transaction.go new file mode 100644 index 000000000..4ba4ba4bb --- /dev/null +++ b/token/services/interop/pledge/transaction.go @@ -0,0 +1,56 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" +) + +type Binder interface { + Bind(longTerm view.Identity, ephemeral view.Identity) error +} + +// Transaction holds a ttx transaction +type Transaction struct { + *ttx.Transaction + Binder Binder +} + +// NewAnonymousTransaction returns a new anonymous token transaction customized with the passed opts +func NewAnonymousTransaction(ctx view.Context, opts ...ttx.TxOption) (*Transaction, error) { + tx, err := ttx.NewAnonymousTransaction(ctx, opts...) + if err != nil { + return nil, err + } + return &Transaction{ + Transaction: tx, + Binder: view2.GetEndpointService(ctx), + }, nil +} + +// NewTransactionFromBytes returns a new transaction from the passed bytes +func NewTransactionFromBytes(ctx view.Context, raw []byte) (*Transaction, error) { + tx, err := ttx.NewTransactionFromBytes(ctx, raw) + if err != nil { + return nil, err + } + return &Transaction{ + Transaction: tx, + Binder: view2.GetEndpointService(ctx), + }, nil +} + +// Outputs returns a new OutputStream of the transaction's outputs +func (t *Transaction) Outputs() (*OutputStream, error) { + outs, err := t.TokenRequest.Outputs() + if err != nil { + return nil, err + } + return NewOutputStream(outs), nil +} diff --git a/token/services/interop/pledge/wallet.go b/token/services/interop/pledge/wallet.go new file mode 100644 index 000000000..e4dddae65 --- /dev/null +++ b/token/services/interop/pledge/wallet.go @@ -0,0 +1,236 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pledge + +import ( + "encoding/json" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view" + view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/network" + token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" +) + +func MyOwnerWallet(sp view.ServiceProvider) (*token.OwnerWallet, error) { + w := token.GetManagementService(sp).WalletManager().OwnerWallet("") + if w == nil { + return nil, errors.Errorf("owner Wallet needs to be initialized") + } + return w, nil +} + +func GetOwnerWallet(sp view.ServiceProvider, id string, opts ...token.ServiceOption) (*token.OwnerWallet, error) { + w := token.GetManagementService(sp, opts...).WalletManager().OwnerWallet(id) + if w == nil { + return nil, errors.Errorf("owner Wallet needs to be initialized") + } + return w, nil +} + +func GetIssuerWallet(sp view.ServiceProvider, id string, opts ...token.ServiceOption) (*token.IssuerWallet, error) { + w := token.GetManagementService(sp, opts...).WalletManager().IssuerWallet(id) + if w == nil { + return nil, errors.Errorf("issuer Wallet needs to be initialized") + } + return w, nil +} + +type QueryService interface { + // TODO: switch to UnspentTokensIteratorBy(id, typ string) (UnspentTokensIterator, error) + ListUnspentTokens() (*token2.UnspentTokens, error) +} + +type IssuerWallet struct { + wallet *token.IssuerWallet + queryService QueryService +} + +func NewIssuerWallet(sp view.ServiceProvider, wallet *token.IssuerWallet) *IssuerWallet { + tmsID := wallet.TMS().ID() + net := network.GetInstance(sp, tmsID.Network, tmsID.Channel) + if net == nil { + logger.Errorf("could not find network [%s]", tmsID) + return nil + } + v, err := net.TokenVault(tmsID.Namespace) + if err != nil { + logger.Errorf("failed to get vault for [%s]: [%s]", tmsID, err) + } + + return &IssuerWallet{ + wallet: wallet, + queryService: v.QueryEngine(), + } +} + +func (w *IssuerWallet) GetPledgedToken(tokenID *token2.ID) (*token2.UnspentToken, *Script, error) { + unspentTokens, err := w.queryService.ListUnspentTokens() + if err != nil { + return nil, nil, errors.Wrap(err, "token selection failed") + } + return retrievePledgedToken(unspentTokens, tokenID) +} + +type OwnerWallet struct { + wallet *token.OwnerWallet + queryService QueryService +} + +// GetWallet returns the wallet whose id is the passed id. +// If the passed id is empty, GetWallet has the same behaviour of MyWallet. +// It returns nil, if no wallet is found. +func GetWallet(sp view.ServiceProvider, id string, opts ...token.ServiceOption) *token.OwnerWallet { + w := token.GetManagementService(sp, opts...).WalletManager().OwnerWallet(id) + if w == nil { + return nil + } + return w +} + +func Wallet(sp view.ServiceProvider, wallet *token.OwnerWallet) *OwnerWallet { + tmsID := wallet.TMS().ID() + net := network.GetInstance(sp, tmsID.Network, tmsID.Channel) + if net == nil { + logger.Errorf("could not find network [%s]", tmsID) + return nil + } + v, err := net.TokenVault(tmsID.Namespace) + if err != nil { + logger.Errorf("failed to get vault for [%s]: [%s]", tmsID, err) + } + + return &OwnerWallet{ + wallet: wallet, + queryService: v.QueryEngine(), + } +} + +func (w *OwnerWallet) GetPledgedToken(tokenID *token2.ID) (*token2.UnspentToken, *Script, error) { + unspentTokens, err := w.queryService.ListUnspentTokens() + if err != nil { + return nil, nil, errors.Wrap(err, "token selection failed") + } + + return retrievePledgedToken(unspentTokens, tokenID) +} + +func retrievePledgedToken(unspentTokens *token2.UnspentTokens, tokenID *token2.ID) (*token2.UnspentToken, *Script, error) { + logger.Debugf("[%d] unspent tokens found, search [%s]", len(unspentTokens.Tokens), tokenID) + + var res []*token2.UnspentToken + var scripts []*Script + for _, tok := range unspentTokens.Tokens { + if tok.Id.String() == tokenID.String() { + owner, err := identity.UnmarshalTypedIdentity(tok.Owner.Raw) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to unmarshal owner") + } + logger.Debugf("found token [%s] with type [%s]", tokenID, owner.Type) + if owner.Type == ScriptType { + res = append(res, tok) + script := &Script{} + if err := json.Unmarshal(owner.Identity, script); err != nil { + return nil, nil, errors.Wrapf(err, "failed unmarshalling pledge script") + } + scripts = append(scripts, script) + } + } + } + if len(res) > 1 { + return nil, nil, errors.Errorf("multiple pledged tokens with the same identifier [%s]", tokenID.String()) + } + if len(res) == 0 { + return nil, nil, errors.Errorf("no pledged token exists with identifier [%s]", tokenID.String()) + } + return res[0], scripts[0], nil +} + +// ScriptAuth implements the Authorization interface for this script +type ScriptAuth struct { + WalletService driver.WalletService +} + +func NewScriptAuth(walletService driver.WalletService) *ScriptAuth { + return &ScriptAuth{WalletService: walletService} +} + +// AmIAnAuditor returns false for script ownership +func (s *ScriptAuth) AmIAnAuditor() bool { + return false +} + +// IsMine returns true if either the sender or the recipient is in one of the owner wallets. +// It returns an empty wallet id. +func (s *ScriptAuth) IsMine(tok *token2.Token) (string, []string, bool) { + identity, err := identity.UnmarshalTypedIdentity(tok.Owner.Raw) + if err != nil { + logger.Debugf("Is Mine [%s,%s,%s]? No, failed unmarshalling [%s]", view2.Identity(tok.Owner.Raw), tok.Type, tok.Quantity, err) + return "", nil, false + } + if identity.Type != ScriptType { + return "", nil, false + } + + script := &Script{} + if err := json.Unmarshal(identity.Identity, script); err != nil { + logger.Debugf("Is Mine [%s,%s,%s]? No, failed unmarshalling [%s]", view2.Identity(tok.Owner.Raw), tok.Type, tok.Quantity, err) + return "", nil, false + } + if script.Sender.IsNone() || script.Recipient.IsNone() || script.Issuer.IsNone() { + logger.Debugf("Is Mine [%s,%s,%s]? No, invalid content [%v]", view2.Identity(tok.Owner.Raw), tok.Type, tok.Quantity, script) + return "", nil, false + } + + // I'm either a sender, recipient, or issuer + var ids []string + for _, beneficiary := range []struct { + identity view2.Identity + desc string + prefix string + }{ + { + identity: script.Sender, + desc: "sender", + prefix: "pledge.sender", + }, + { + identity: script.Recipient, + desc: "recipient", + prefix: "pledge.recipient", + }, + { + identity: script.Issuer, + desc: "issuer", + prefix: "pledge.issuer", + }, + } { + logger.Debugf("Is Mine [%s,%s,%s] as a %s?", view2.Identity(tok.Owner.Raw), tok.Type, tok.Quantity, beneficiary.desc) + // TODO: differentiate better + if wallet, err := s.WalletService.OwnerWallet(beneficiary.identity); err == nil { + logger.Debugf("Is Mine [%s,%s,%s] as a %s? Yes", view2.Identity(tok.Owner.Raw), tok.Type, tok.Quantity, beneficiary.desc) + ids = append(ids, beneficiary.prefix+wallet.ID()) + } + } + logger.Debugf("Is Mine [%s,%s,%s]? [%b] with [%s]", view2.Identity(tok.Owner.Raw), tok.Type, tok.Quantity, len(ids) != 0, ids) + return "", ids, len(ids) != 0 +} + +func (s *ScriptAuth) Issued(issuer driver.Identity, tok *token2.Token) bool { + return false +} + +func (s *ScriptAuth) OwnerType(raw []byte) (string, []byte, error) { + owner, err := identity.UnmarshalTypedIdentity(raw) + if err != nil { + return "", nil, err + } + return owner.Type, owner.Identity, nil +} diff --git a/token/services/interop/state/driver/driver.go b/token/services/interop/state/driver/driver.go new file mode 100644 index 000000000..e053f3900 --- /dev/null +++ b/token/services/interop/state/driver/driver.go @@ -0,0 +1,61 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package driver + +import ( + "time" + + token2 "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" +) + +// StateQueryExecutor models a prover of token related states +type StateQueryExecutor interface { + // Exist returns a proof that the passed token exists in the network this query executor targets + Exist(tokenID *token.ID) ([]byte, error) + // DoesNotExist returns a proof that the passed token, originated in the given network, does not exist + // in the network this query executor targets + DoesNotExist(tokenID *token.ID, origin string, deadline time.Time) ([]byte, error) + // ExistsWithMetadata returns a proof that a token with metadata including the passed token ID and origin network exists + // in the network this query executor targets + ExistsWithMetadata(tokenID *token.ID, origin string) ([]byte, error) +} + +// StateVerifier is used to verify proofs related to the state of tokens in a target network +type StateVerifier interface { + // VerifyProofExistence verifies that a proof of existence of the passed token in the target network is valid + VerifyProofExistence(proof []byte, tokenID *token.ID, metadata []byte) error + // VerifyProofNonExistence verifies that a proof of non-existence of the given token, + // originated in the given network, in the target network is valid + VerifyProofNonExistence(proof []byte, tokenID *token.ID, origin string, deadline time.Time) error + // VerifyProofTokenWithMetadataExistence verifies that a proof of existence of a token + // with metadata including the given token ID and origin network, in the target network is valid + VerifyProofTokenWithMetadataExistence(proof []byte, tokenID *token.ID, origin string) error +} + +// StateServiceProvider manages state-related services +type StateServiceProvider interface { + // QueryExecutor returns an instance of a query executor to requests proofs from the network identified by the passed url + QueryExecutor(url string) (StateQueryExecutor, error) + // Verifier returns an instance of a verifier of proofs generated by the network identified by the passed url + Verifier(url string) (StateVerifier, error) + // TODO: comment + URLToTMSID(url string) (token2.TMSID, error) +} + +type SSPDriverName string + +type NamedSSPDriver struct { + Name SSPDriverName + Driver SSPDriver +} + +// SSPDriver models a driver factory for state-related services +type SSPDriver interface { + // New returns an instance of a state service provider + New() (StateServiceProvider, error) +} diff --git a/token/services/interop/state/fabric/core/state.go b/token/services/interop/state/fabric/core/state.go new file mode 100644 index 000000000..8e8ad84e9 --- /dev/null +++ b/token/services/interop/state/fabric/core/state.go @@ -0,0 +1,454 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package core + +import ( + "encoding/base64" + "encoding/json" + "strings" + "time" + + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state/driver" + fabric3 "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state/fabric" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/logging" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/network/common/rws/translator" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/network/fabric/tcc" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" +) + +// KeyTranslator is used to translate tokens' concepts into backend's keys. +type KeyTranslator interface { + CreateProofOfExistenceKey(tokenId *token.ID) (string, error) + CreateProofOfNonExistenceKey(tokenID *token.ID, origin string) (string, error) + CreateProofOfMetadataExistenceKey(tokenID *token.ID, origin string) (string, error) +} + +type Validator interface { + Validate(tok []byte, info *pledge.Info) error +} + +type PledgeVault interface { + PledgeByTokenID(tokenID *token.ID) (*pledge.Info, error) +} + +type GetFabricNetworkServiceFunc = func(string) (*fabric.NetworkService, error) + +type StateQueryExecutor struct { + Logger logging.Logger + RelayProvider fabric3.RelayProvider + TargetNetworkURL string + RelaySelector *fabric.NetworkService +} + +func NewStateQueryExecutor( + Logger logging.Logger, + RelayProvider fabric3.RelayProvider, + targetNetworkURL string, + relaySelector *fabric.NetworkService, +) (*StateQueryExecutor, error) { + if err := fabric3.CheckFabricScheme(targetNetworkURL); err != nil { + return nil, err + } + return &StateQueryExecutor{ + Logger: Logger, + RelayProvider: RelayProvider, + TargetNetworkURL: targetNetworkURL, + RelaySelector: relaySelector, + }, nil +} + +func (p *StateQueryExecutor) Exist(tokenID *token.ID) ([]byte, error) { + raw, err := json.Marshal(tokenID) + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling tokenID") + } + + // get local relay + relay := p.RelayProvider.Relay(p.RelaySelector) + + // Query + p.Logger.Debugf("Query [%s] for proof of existence of token [%s], input [%s]", p.TargetNetworkURL, tokenID.String(), base64.StdEncoding.EncodeToString(raw)) + query, err := relay.ToFabric().Query( + p.TargetNetworkURL, + tcc.ProofOfTokenExistenceQuery, + base64.StdEncoding.EncodeToString(raw), + ) + if err != nil { + return nil, errors.Wrapf(err, "failed querying token") + } + res, err := query.Call() + if err != nil { + // todo: move this to the query executor + errMsg := err.Error() + switch { + case strings.Contains(errMsg, "failed to confirm if token with ID"): + return nil, errors.WithMessagef(state.TokenDoesNotExistError, "%s", err) + default: + return nil, err + } + } + + return res.Proof() +} + +func (p *StateQueryExecutor) DoesNotExist(tokenID *token.ID, origin string, deadline time.Time) ([]byte, error) { + req := &tcc.ProofOfTokenNonExistenceRequest{ + Deadline: deadline, + OriginNetwork: origin, + TokenID: tokenID, + } + raw, err := json.Marshal(req) + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling tokenID") + } + + // get local relay + relay := p.RelayProvider.Relay(p.RelaySelector) + + // Query + p.Logger.Debugf("Query [%s] for proof of non-existence of token [%s], input [%s]", p.TargetNetworkURL, tokenID.String(), base64.StdEncoding.EncodeToString(raw)) + query, err := relay.ToFabric().Query( + p.TargetNetworkURL, + tcc.ProofOfTokenNonExistenceQuery, + base64.StdEncoding.EncodeToString(raw), + ) + if err != nil { + return nil, errors.Wrapf(err, "failed querying token") + } + res, err := query.Call() + if err != nil { + errMsg := err.Error() + switch { + case strings.Contains(errMsg, "failed to confirm if token from network"): + return nil, errors.WithMessagef(state.TokenExistsError, "%s", err) + default: + return nil, err + } + } + + return res.Proof() +} + +// ExistsWithMetadata returns a proof that a token with metadata including the passed token ID and origin network exists +// in the network this query executor targets +func (p *StateQueryExecutor) ExistsWithMetadata(tokenID *token.ID, origin string) ([]byte, error) { + req := &tcc.ProofOfTokenMetadataExistenceRequest{ + OriginNetwork: origin, + TokenID: tokenID, + } + raw, err := json.Marshal(req) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal proof of token metadata [%s]", req) + } + + // Get local relay + relay := p.RelayProvider.Relay(p.RelaySelector) + + // Query + p.Logger.Debugf("Query [%s] for proof of existence of metadata with token [%s], input [%s]", p.TargetNetworkURL, tokenID.String(), base64.StdEncoding.EncodeToString(raw)) + query, err := relay.ToFabric().Query( + p.TargetNetworkURL, + tcc.ProofOfTokenMetadataExistenceQuery, + base64.StdEncoding.EncodeToString(raw), + ) + if err != nil { + return nil, errors.Wrapf(err, "failed to query proof of metadata [%s]", req) + } + res, err := query.Call() + if err != nil { + errMsg := err.Error() + switch { + case strings.Contains(errMsg, "failed to confirm if token from network"): + return nil, errors.WithMessagef(state.TokenExistsError, "%s", err) + default: + return nil, err + } + } + + return res.Proof() +} + +type StateVerifier struct { + Logger logging.Logger + RelayProvider fabric3.RelayProvider + NetworkURL string + RelaySelector *fabric.NetworkService + PledgeVault PledgeVault + GetFabricNetworkService GetFabricNetworkServiceFunc + validator Validator + keyTranslator KeyTranslator +} + +func NewStateVerifier( + Logger logging.Logger, + relayProvider fabric3.RelayProvider, + PledgeVault PledgeVault, + GetFabricNetworkService GetFabricNetworkServiceFunc, + networkURL string, + relaySelector *fabric.NetworkService, + validator Validator, + keyTranslator KeyTranslator, +) (*StateVerifier, error) { + if err := fabric3.CheckFabricScheme(networkURL); err != nil { + return nil, err + } + return &StateVerifier{ + Logger: Logger, + RelayProvider: relayProvider, + NetworkURL: networkURL, + RelaySelector: relaySelector, + PledgeVault: PledgeVault, + GetFabricNetworkService: GetFabricNetworkService, + validator: validator, + keyTranslator: keyTranslator, + }, nil +} + +func (v *StateVerifier) VerifyProofExistence(proofRaw []byte, tokenID *token.ID, metadata []byte) error { + // Get local relay + relay := v.RelayProvider.Relay(v.RelaySelector) + + // Parse proof + proof, err := relay.ToFabric().ProofFromBytes(proofRaw) + if err != nil { + return errors.Wrapf(err, "failed to unmarshal claim proof") + } + if err := proof.Verify(); err != nil { + return errors.Wrapf(err, "failed to verify pledge proof") + } + + // todo check that address in proof matches source network + + rwset, err := proof.RWSet() + if err != nil { + return errors.Wrapf(err, "failed to unmarshal claim proof") + } + + key, err := v.keyTranslator.CreateProofOfExistenceKey(tokenID) + if err != nil { + return errors.Wrapf(err, "failed to create proof of existence key from token [%s]", tokenID) + } + tmsID, err := fabric3.FabricURLToTMSID(v.NetworkURL) + if err != nil { + return errors.Wrapf(err, "failed to extract tms id from [%s]", v.NetworkURL) + } + raw, err := rwset.GetState(tmsID.Namespace, key) + if err != nil { + return errors.Wrapf(err, "failed to get state for token [%s:%s]", tmsID.Namespace, key) + } + if len(raw) == 0 { + return errors.Errorf("token [%s:%s] does not contain proof", tmsID.Namespace, key) + } + // Validate against pledge + v.Logger.Debugf("verify proof of existence for token id [%s]", tokenID) + pledge, err := v.PledgeVault.PledgeByTokenID(tokenID) + if err != nil { + v.Logger.Errorf("failed retrieving pledge info for token id [%s]: [%s]", tokenID, err) + return errors.WithMessagef(err, "failed getting pledge for [%s]", tokenID) + } + if pledge == nil { + v.Logger.Errorf("failed retrieving pledge info for token id [%s]: no info found", tokenID) + return errors.Errorf("expected one pledge, got nil") + } + v.Logger.Debugf("found pledge info for token id [%s]: [%s]", tokenID, pledge.Source) + + // validate + if err := v.validator.Validate(raw, pledge); err != nil { + return errors.Wrapf(err, "failed to check token") + } + + return nil +} + +func (v *StateVerifier) VerifyProofNonExistence(proofRaw []byte, tokenID *token.ID, origin string, deadline time.Time) error { + // v.NetworkURL is the network from which the proof comes from + tokenOriginNetworkTMSID, err := fabric3.FabricURLToTMSID(origin) + if err != nil { + return errors.Wrapf(err, "failed to parse network url") + } + // get local relay + fns, err := v.GetFabricNetworkService(tokenOriginNetworkTMSID.Network) + if err != nil { + return errors.Wrapf(err, "failed to get fabric network service for network [%s]", tokenOriginNetworkTMSID.Network) + } + relay := v.RelayProvider.Relay(fns) + + // parse proof + proof, err := relay.ToFabric().ProofFromBytes(proofRaw) + if err != nil { + return errors.Wrapf(err, "failed to umarshal proof") + } + + rwset, err := proof.RWSet() + if err != nil { + return errors.Wrapf(err, "failed to retrieve RWset") + } + + key, err := v.keyTranslator.CreateProofOfNonExistenceKey(tokenID, origin) + if err != nil { + return errors.Wrapf(err, "failed creating key for proof of non-existence") + } + + proofSourceNetworkTMSID, err := fabric3.FabricURLToTMSID(v.NetworkURL) + if err != nil { + return err + } + raw, err := rwset.GetState(proofSourceNetworkTMSID.Namespace, key) + if err != nil { + return errors.Wrapf(err, "failed to check proof of non-existence") + } + p := &translator.ProofOfTokenMetadataNonExistence{} + if raw == nil { + return errors.Errorf("could not find proof of non-existence") + } + err = json.Unmarshal(raw, p) + if err != nil { + return errors.Wrapf(err, "failed to unmarshal proof of non-existence") + } + if p.Deadline != deadline { + return errors.Errorf("deadline in reclaim request does not match deadline in proof of non-existence") + } + if p.TokenID.String() != tokenID.String() { + return errors.Errorf("token ID in reclaim request does not match token ID in proof of non-existence") + } + if p.Origin != fabric3.FabricURL(tokenOriginNetworkTMSID) { + return errors.Errorf("origin in reclaim request does not match origin in proof of non-existence") + } + + // todo check that address in proof is the destination network + + err = proof.Verify() + if err != nil { + return errors.Wrapf(err, "invalid proof of non-existence") + } + + return nil +} + +// VerifyProofTokenWithMetadataExistence verifies that a proof of existence of a token +// with metadata including the given token ID and origin network, in the target network is valid +func (v *StateVerifier) VerifyProofTokenWithMetadataExistence(proofRaw []byte, tokenID *token.ID, origin string) error { + // v.NetworkURL is the network from which the proof comes from + tokenOriginNetworkTMSID, err := fabric3.FabricURLToTMSID(origin) + if err != nil { + return errors.Wrapf(err, "failed to parse network url") + } + + // get local relay + fns, err := v.GetFabricNetworkService(tokenOriginNetworkTMSID.Network) + if err != nil { + return errors.Wrapf(err, "failed to get fabric network service for network [%s]", tokenOriginNetworkTMSID.Network) + } + relay := v.RelayProvider.Relay(fns) + + // parse proof + proof, err := relay.ToFabric().ProofFromBytes(proofRaw) + if err != nil { + return errors.Wrapf(err, "failed to umarshal proof") + } + + rwset, err := proof.RWSet() + if err != nil { + return errors.Wrapf(err, "failed to retrieve RWset") + } + + key, err := v.keyTranslator.CreateProofOfMetadataExistenceKey(tokenID, origin) + if err != nil { + return errors.Wrapf(err, "failed creating key for proof of token existence") + } + + proofSourceNetworkTMSID, err := fabric3.FabricURLToTMSID(v.NetworkURL) + if err != nil { + return err + } + raw, err := rwset.GetState(proofSourceNetworkTMSID.Namespace, key) + if err != nil { + return errors.Wrapf(err, "failed to check proof of token existence") + } + p := &translator.ProofOfTokenMetadataExistence{} + if raw == nil { + return errors.Errorf("could not find proof of token existence") + } + err = json.Unmarshal(raw, p) + if err != nil { + return errors.Wrapf(err, "failed to unmarshal proof of token existence") + } + if p.TokenID.String() != tokenID.String() { + return errors.Errorf("token ID in redeem request does not match token ID in proof of token existence") + } + if p.Origin != fabric3.FabricURL(tokenOriginNetworkTMSID) { + return errors.Errorf("origin in redeem request does not match origin in proof of token existence") + } + + // todo check that address in proof is the destination network + + err = proof.Verify() + if err != nil { + return errors.Wrapf(err, "invalid proof of token existence") + } + + return nil +} + +type StateDriver struct { + Logger logging.Logger + FNSProvider *fabric.NetworkServiceProvider + RelayProvider fabric3.RelayProvider + VaultStore *pledge.VaultStore + Validator Validator + KeyTranslator KeyTranslator +} + +func NewStateDriver( + logger logging.Logger, + FNSProvider *fabric.NetworkServiceProvider, + relayProvider fabric3.RelayProvider, + vaultStore *pledge.VaultStore, + validator Validator, + keyTranslator KeyTranslator, +) *StateDriver { + return &StateDriver{ + Logger: logger, + FNSProvider: FNSProvider, + RelayProvider: relayProvider, + VaultStore: vaultStore, + Validator: validator, + KeyTranslator: keyTranslator, + } +} + +func (d *StateDriver) NewStateQueryExecutor(url string) (driver.StateQueryExecutor, error) { + fns, err := d.FNSProvider.FabricNetworkService("") + if err != nil { + return nil, errors.Wrapf(err, "failed to get default FNS") + } + + return NewStateQueryExecutor(d.Logger, d.RelayProvider, url, fns) +} + +func (d *StateDriver) NewStateVerifier(url string) (driver.StateVerifier, error) { + fns, err := d.FNSProvider.FabricNetworkService("") + if err != nil { + return nil, errors.Wrapf(err, "failed to get default FNS") + } + return NewStateVerifier( + d.Logger, + d.RelayProvider, + d.VaultStore, + func(id string) (*fabric.NetworkService, error) { + return d.FNSProvider.FabricNetworkService(id) + }, + url, + fns, + d.Validator, + d.KeyTranslator, + ) +} diff --git a/token/services/interop/state/fabric/driver.go b/token/services/interop/state/fabric/driver.go new file mode 100644 index 000000000..1d7a627ff --- /dev/null +++ b/token/services/interop/state/fabric/driver.go @@ -0,0 +1,25 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fabric + +import ( + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state/driver" +) + +type StateDriver interface { + // NewStateQueryExecutor returns a new StateQueryExecutor for the given URL + NewStateQueryExecutor(url string) (driver.StateQueryExecutor, error) + // NewStateVerifier returns a new StateVerifier for the given url + NewStateVerifier(url string) (driver.StateVerifier, error) +} + +type StateDriverName string + +type NamedStateDriver struct { + Name StateDriverName + Driver StateDriver +} diff --git a/token/services/interop/state/fabric/ssp.go b/token/services/interop/state/fabric/ssp.go new file mode 100644 index 000000000..e0a702b1a --- /dev/null +++ b/token/services/interop/state/fabric/ssp.go @@ -0,0 +1,201 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fabric + +import ( + "sync" + + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/services/weaver" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state/driver" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/logging" + "github.com/pkg/errors" + "go.uber.org/dig" +) + +const ( + QueryPublicParamsFunction = "queryPublicParams" + + DefaultFabricNetworkName = "" +) + +var logger = logging.MustGetLogger("token-sdk.state") + +type RelayProvider interface { + Relay(fns *fabric.NetworkService) *weaver.Relay +} + +type StateServiceProvider struct { + drivers map[StateDriverName]NamedStateDriver + fnsProvider *fabric.NetworkServiceProvider + relayProvider RelayProvider + tmsProvider *token.ManagementServiceProvider + + mu sync.RWMutex + queryExecutors map[string]driver.StateQueryExecutor + verifiers map[string]driver.StateVerifier +} + +func NewStateServiceProvider( + drivers map[StateDriverName]NamedStateDriver, + fnsProvider *fabric.NetworkServiceProvider, + weaverProvider RelayProvider, + tmsProvider *token.ManagementServiceProvider, +) *StateServiceProvider { + return &StateServiceProvider{ + drivers: drivers, + fnsProvider: fnsProvider, + relayProvider: weaverProvider, + tmsProvider: tmsProvider, + mu: sync.RWMutex{}, + queryExecutors: map[string]driver.StateQueryExecutor{}, + verifiers: map[string]driver.StateVerifier{}, + } +} + +func (f *StateServiceProvider) RegisterDriver(driver NamedStateDriver) { + f.drivers[driver.Name] = driver +} + +func (f *StateServiceProvider) QueryExecutor(url string) (driver.StateQueryExecutor, error) { + f.mu.Lock() + defer f.mu.Unlock() + + qe, ok := f.queryExecutors[url] + if ok { + return qe, nil + } + + // Fetch public parameters, if not fetched already + ppRaw, err := f.fetchPublicParameters(url) + if err != nil { + return nil, errors.Wrapf(err, "failed fetching public parameters from [%s]", url) + } + pp, err := f.tmsProvider.PublicParametersFromBytes(ppRaw) + if err != nil { + return nil, errors.Wrapf(err, "failed unmarshalling public parameters from [%s]", url) + } + + driver, ok := f.drivers[StateDriverName(pp.Identifier())] + if !ok { + return nil, errors.Errorf("invalid public parameters type, got [%s]", pp.Identifier()) + } + qe, err = driver.Driver.NewStateQueryExecutor(url) + if err != nil { + return nil, errors.Wrapf(err, "failed instantiating state query executor from [%s]", url) + } + v, err := driver.Driver.NewStateVerifier(url) + if err != nil { + return nil, errors.Wrapf(err, "failed instantiating state verifier from [%s]", url) + } + f.queryExecutors[url] = qe + f.verifiers[url] = v + + return qe, nil +} + +func (f *StateServiceProvider) Verifier(url string) (driver.StateVerifier, error) { + f.mu.Lock() + defer f.mu.Unlock() + + v, ok := f.verifiers[url] + if ok { + return v, nil + } + + var identifier string + + // Check if the url refers to a TMS known by this node, then create and return just a verifier + tmsID, err := FabricURLToTMSID(url) + if err != nil { + return nil, errors.Wrapf(err, "failed parsing url [%s]", url) + } + tms, err := f.tmsProvider.GetManagementService(token.WithTMSID(tmsID)) + if err != nil { + // If not, fetch public parameters, if not fetched already + ppRaw, err := f.fetchPublicParameters(url) + if err != nil { + return nil, errors.Wrapf(err, "failed fetching public parameters from [%s]", url) + } + pp, err := f.tmsProvider.PublicParametersFromBytes(ppRaw) + if err != nil { + return nil, errors.Wrapf(err, "failed unmarshalling public parameters from [%s]", url) + } + identifier = pp.Identifier() + } else { + identifier = tms.PublicParametersManager().PublicParameters().Identifier() + } + + driver, ok := f.drivers[StateDriverName(identifier)] + if !ok { + return nil, errors.Errorf("invalid public parameters type, got [%s]", identifier) + } + v, err = driver.Driver.NewStateVerifier(url) + if err != nil { + return nil, errors.Wrapf(err, "failed instantiating state verifier from [%s]", url) + } + f.verifiers[url] = v + + return v, nil +} + +func (f *StateServiceProvider) URLToTMSID(url string) (token.TMSID, error) { + return FabricURLToTMSID(url) +} + +func (f *StateServiceProvider) fetchPublicParameters(url string) ([]byte, error) { + fns, err := f.fnsProvider.FabricNetworkService(DefaultFabricNetworkName) + if err != nil { + return nil, errors.Wrapf(err, "failed getting default FNS for [%s]", url) + } + relay := f.relayProvider.Relay(fns) + logger.Debugf("query [%s] for the public parameters", url) + + query, err := relay.ToFabric().Query(url, QueryPublicParamsFunction) + if err != nil { + return nil, err + } + res, err := query.Call() + if err != nil { + return nil, err + } + return res.Result(), nil +} + +type SSPDriver struct { + stateDrivers map[StateDriverName]NamedStateDriver + fnsProvider *fabric.NetworkServiceProvider + weaverProvider RelayProvider + tmsProvider *token.ManagementServiceProvider +} + +func NewSSPDriver(in struct { + dig.In + StateDriversList []NamedStateDriver `group:"fabric-ssp-state-drivers"` + FNSProvider *fabric.NetworkServiceProvider + WeaverProvider RelayProvider + TMSProvider *token.ManagementServiceProvider +}) driver.NamedSSPDriver { + stateDrivers := map[StateDriverName]NamedStateDriver{} + for _, stateDriver := range in.StateDriversList { + stateDrivers[stateDriver.Name] = stateDriver + } + return driver.NamedSSPDriver{ + Name: "fabric", + Driver: &SSPDriver{ + stateDrivers: stateDrivers, + fnsProvider: in.FNSProvider, + weaverProvider: in.WeaverProvider, + tmsProvider: in.TMSProvider, + }, + } +} + +func (f *SSPDriver) New() (driver.StateServiceProvider, error) { + return NewStateServiceProvider(f.stateDrivers, f.fnsProvider, f.weaverProvider, f.tmsProvider), nil +} diff --git a/token/services/interop/state/fabric/url.go b/token/services/interop/state/fabric/url.go new file mode 100644 index 000000000..68734921d --- /dev/null +++ b/token/services/interop/state/fabric/url.go @@ -0,0 +1,38 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fabric + +import ( + "fmt" + url2 "net/url" + "strings" + + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/pkg/errors" +) + +func FabricURL(tms token.TMSID) string { + return fmt.Sprintf("fabric://%s.%s.%s/", tms.Network, tms.Channel, tms.Namespace) +} + +func FabricURLToTMSID(url string) (token.TMSID, error) { + u, err := url2.Parse(url) + if err != nil { + return token.TMSID{}, errors.Wrapf(err, "failed parsing url") + } + if u.Scheme != "fabric" { + return token.TMSID{}, errors.Errorf("invalid scheme, expected fabric, got [%s]", u.Scheme) + } + + res := strings.Split(u.Host, ".") + if len(res) != 3 { + return token.TMSID{}, errors.Errorf("invalid host, expected 3 components, found [%d,%v]", len(res), res) + } + return token.TMSID{ + Network: res[0], Channel: res[1], Namespace: res[2], + }, nil +} diff --git a/token/services/interop/state/fabric/utils.go b/token/services/interop/state/fabric/utils.go new file mode 100644 index 000000000..205cbff17 --- /dev/null +++ b/token/services/interop/state/fabric/utils.go @@ -0,0 +1,25 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fabric + +import ( + url2 "net/url" + + "github.com/pkg/errors" +) + +// CheckFabricScheme returns an error is the given url is not valid or its scheme is not equal to fabric +func CheckFabricScheme(url string) error { + u, err := url2.Parse(url) + if err != nil { + return errors.Wrapf(err, "failed parsing url [%s]", url) + } + if u.Scheme != "fabric" { + return errors.Errorf("invalid scheme, expected fabric, got [%s] in url [%s]", u.Scheme, url) + } + return nil +} diff --git a/token/services/interop/state/state.go b/token/services/interop/state/state.go new file mode 100644 index 000000000..87d429a27 --- /dev/null +++ b/token/services/interop/state/state.go @@ -0,0 +1,99 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package state + +import ( + url2 "net/url" + "sync" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/flogging" + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state/driver" + "github.com/pkg/errors" +) + +var ( + logger = flogging.MustGetLogger("token-sdk.services.interop.state") + // TokenExistsError is returned when the token already exists + TokenExistsError = errors.New("token exists") + // TokenDoesNotExistError is returned when the token does not exist + TokenDoesNotExistError = errors.New("token does not exists") +) + +type ServiceProvider struct { + sspsMu sync.RWMutex + ssps map[string]driver.StateServiceProvider + sspDrivers map[driver.SSPDriverName]driver.NamedSSPDriver +} + +func NewServiceProvider() *ServiceProvider { + return &ServiceProvider{ + ssps: map[string]driver.StateServiceProvider{}, + sspDrivers: map[driver.SSPDriverName]driver.NamedSSPDriver{}, + } +} + +func (p *ServiceProvider) RegisterDriver(driver driver.NamedSSPDriver) { + logger.Debugf("register driver [%s]", driver.Name) + p.sspDrivers[driver.Name] = driver +} + +func (p *ServiceProvider) QueryExecutor(url string) (driver.StateQueryExecutor, error) { + ssp, err := p.ssp(url) + if err != nil { + return nil, errors.WithMessagef(err, "failed to get ssp for url [%s]", url) + } + return ssp.QueryExecutor(url) +} + +func (p *ServiceProvider) Verifier(url string) (driver.StateVerifier, error) { + ssp, err := p.ssp(url) + if err != nil { + return nil, errors.WithMessagef(err, "failed to get ssp for url [%s]", url) + } + return ssp.Verifier(url) +} + +func (p *ServiceProvider) URLToTMSID(url string) (token.TMSID, error) { + ssp, err := p.ssp(url) + if err != nil { + return token.TMSID{}, errors.WithMessagef(err, "failed to get ssp for url [%s]", url) + } + return ssp.URLToTMSID(url) +} + +func (p *ServiceProvider) ssp(url string) (driver.StateServiceProvider, error) { + p.sspsMu.Lock() + defer p.sspsMu.Unlock() + + ssp, ok := p.ssps[url] + if !ok { + u, err := url2.Parse(url) + if err != nil { + return nil, errors.Wrapf(err, "failed parsing url") + } + provider, ok := p.sspDrivers[driver.SSPDriverName(u.Scheme)] + if !ok { + return nil, errors.Errorf("invalid scheme, expected fabric, got [%s]", u.Scheme) + } + ssp, err = provider.Driver.New() + if err != nil { + return nil, errors.Wrapf(err, "failed getting state service provider for [%s]", u.Scheme) + } + p.ssps[url] = ssp + } + return ssp, nil +} + +// GetServiceProvider returns an instance of a state service provider +func GetServiceProvider(sp token.ServiceProvider) (*ServiceProvider, error) { + s, err := sp.GetService(&ServiceProvider{}) + if err != nil { + return nil, errors.Wrap(err, "failed getting state service provider") + } + return s.(*ServiceProvider), nil +} diff --git a/token/services/network/common/rws/keys/keys.go b/token/services/network/common/rws/keys/keys.go index 571b5bece..968025318 100644 --- a/token/services/network/common/rws/keys/keys.go +++ b/token/services/network/common/rws/keys/keys.go @@ -13,7 +13,9 @@ import ( "strconv" "unicode/utf8" + token2 "github.com/hyperledger-labs/fabric-token-sdk/token" "github.com/hyperledger-labs/fabric-token-sdk/token/services/network/common/rws/translator" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" "github.com/pkg/errors" ) @@ -30,6 +32,10 @@ const ( InputSerialNumberPrefix = "sn" IssueActionMetadataPrefix = "iam" TransferActionMetadataPrefix = "tam" + + ProofOfExistencePrefix = "pe" + ProofOfNonExistencePrefix = "pne" + ProofOfMetadataExistencePrefix = "pme" ) type Translator struct { @@ -88,6 +94,25 @@ func (t *Translator) CreateTransferActionMetadataKey(key string) (translator.Key return createCompositeKey(TransferActionMetadataPrefix, []string{key}) } +func (t *Translator) CreateProofOfExistenceKey(tokenId *token.ID) (string, error) { + id := token2.Hashable(tokenId.String()).String() + return createCompositeKey(ProofOfExistencePrefix, []string{id}) +} + +func (t *Translator) CreateProofOfNonExistenceKey(tokenID *token.ID, origin string) (string, error) { + return createCompositeKey(ProofOfNonExistencePrefix, []string{ + token2.Hashable(tokenID.String()).String(), + token2.Hashable(origin).String(), + }) +} + +func (t *Translator) CreateProofOfMetadataExistenceKey(tokenID *token.ID, origin string) (string, error) { + return createCompositeKey(ProofOfMetadataExistencePrefix, []string{ + token2.Hashable(tokenID.String()).String(), + token2.Hashable(origin).String(), + }) +} + // createCompositeKey and its related functions and consts copied from core/chaincode/shim/chaincode.go func createCompositeKey(objectType string, attributes []string) (translator.Key, error) { if err := validateCompositeKeyAttribute(objectType); err != nil { diff --git a/token/services/network/common/rws/translator/prover.go b/token/services/network/common/rws/translator/prover.go new file mode 100644 index 000000000..bae2bf512 --- /dev/null +++ b/token/services/network/common/rws/translator/prover.go @@ -0,0 +1,127 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package translator + +import ( + "encoding/json" + "time" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/hash" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" +) + +type Metadata struct { + // OriginTokenID is the identifier of the pledged token in the origin network + OriginTokenID *token.ID + // OriginNetwork is the network where the pledge took place + OriginNetwork string +} + +type ProofOfTokenMetadataNonExistence struct { + Origin string + TokenID *token.ID + Deadline time.Time +} + +type ProofOfTokenMetadataExistence struct { + Origin string + TokenID *token.ID +} + +// ProveTokenExists queries whether a token with the given token ID exists +func (w *Translator) ProveTokenExists(tokenId *token.ID) error { + key, err := w.KeyTranslator.CreateOutputKey(tokenId.TxId, tokenId.Index) + if err != nil { + return err + } + tok, err := w.RWSet.GetState(key) + if err != nil { + return err + } + if tok == nil { + return errors.Errorf("value at key [%s] is empty", tokenId) + } + key, err = w.KeyTranslator.CreateProofOfExistenceKey(tokenId) + if err != nil { + return err + } + err = w.RWSet.SetState(key, tok) + if err != nil { + return err + } + return nil +} + +// ProveTokenDoesNotExist queries whether a token with metadata including the given token ID and origin network does not exist +func (w *Translator) ProveTokenDoesNotExist(tokenID *token.ID, origin string, deadline time.Time) error { + if time.Now().Before(deadline) { + return errors.Errorf("deadline has not elapsed yet") + } + metadata, err := json.Marshal(&Metadata{OriginTokenID: tokenID, OriginNetwork: origin}) + if err != nil { + return errors.Errorf("failed to marshal token metadata") + } + key, err := w.KeyTranslator.CreateIssueActionMetadataKey(hash.Hashable(metadata).String()) + if err != nil { + return err + } + tok, err := w.RWSet.GetState(key) + if err != nil { + return err + } + if tok != nil { + return errors.Errorf("value at key [%s] is not empty", key) + } + proof := &ProofOfTokenMetadataNonExistence{Origin: origin, TokenID: tokenID, Deadline: deadline} + raw, err := json.Marshal(proof) + if err != nil { + return err + } + key, err = w.KeyTranslator.CreateProofOfNonExistenceKey(tokenID, origin) + if err != nil { + return err + } + err = w.RWSet.SetState(key, raw) + if err != nil { + return err + } + return nil +} + +// ProveTokenWithMetadataExists queries whether a token with metadata including the given token ID and origin network exists +func (w *Translator) ProveTokenWithMetadataExists(tokenID *token.ID, origin string) error { + metadata, err := json.Marshal(&Metadata{OriginTokenID: tokenID, OriginNetwork: origin}) + if err != nil { + return errors.Errorf("failed to marshal token metadata") + } + key, err := w.KeyTranslator.CreateIssueActionMetadataKey(hash.Hashable(metadata).String()) + if err != nil { + return err + } + tok, err := w.RWSet.GetState(key) + if err != nil { + return err + } + if tok == nil { + return errors.Errorf("value at key [%s] is empty", key) + } + proof := &ProofOfTokenMetadataExistence{Origin: origin, TokenID: tokenID} + raw, err := json.Marshal(proof) + if err != nil { + return err + } + key, err = w.KeyTranslator.CreateProofOfMetadataExistenceKey(tokenID, origin) + if err != nil { + return err + } + err = w.RWSet.SetState(key, raw) + if err != nil { + return err + } + return nil +} diff --git a/token/services/network/common/rws/translator/rwset.go b/token/services/network/common/rws/translator/rwset.go index b4efabad7..34be999da 100644 --- a/token/services/network/common/rws/translator/rwset.go +++ b/token/services/network/common/rws/translator/rwset.go @@ -11,6 +11,7 @@ import ( "github.com/gobuffalo/packr/v2/file/resolver/encoding/hex" "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" ) type ( @@ -42,6 +43,10 @@ type KeyTranslator interface { CreateTransferActionMetadataKey(subkey string) (Key, error) // GetTransferMetadataSubKey returns the subkey in the given transfer action metadata key GetTransferMetadataSubKey(k string) (Key, error) + + CreateProofOfExistenceKey(tokenId *token.ID) (string, error) + CreateProofOfNonExistenceKey(tokenID *token.ID, origin string) (string, error) + CreateProofOfMetadataExistenceKey(tokenID *token.ID, origin string) (string, error) } // RWSet interface, used to read from, and write to, a rwset. @@ -123,6 +128,21 @@ type HashedKeyTranslator struct { KT KeyTranslator } +func (h *HashedKeyTranslator) CreateProofOfExistenceKey(tokenId *token.ID) (string, error) { + // TODO implement me + panic("implement me") +} + +func (h *HashedKeyTranslator) CreateProofOfNonExistenceKey(tokenID *token.ID, origin string) (string, error) { + // TODO implement me + panic("implement me") +} + +func (h *HashedKeyTranslator) CreateProofOfMetadataExistenceKey(tokenID *token.ID, origin string) (string, error) { + // TODO implement me + panic("implement me") +} + func (h *HashedKeyTranslator) CreateTokenRequestKey(id string) (Key, error) { k, err := h.KT.CreateTokenRequestKey(id) if err != nil { diff --git a/token/services/network/driver/network.go b/token/services/network/driver/network.go index 48c077595..c54fdad21 100644 --- a/token/services/network/driver/network.go +++ b/token/services/network/driver/network.go @@ -97,3 +97,7 @@ type Network interface { // ProcessNamespace indicates to the commit pipeline to process all transaction in the passed namespace ProcessNamespace(namespace string) error } + +type Interoperability interface { + InteropURL(namespace string) string +} diff --git a/token/services/network/fabric/network.go b/token/services/network/fabric/network.go index 2afa651d4..a932c2547 100644 --- a/token/services/network/fabric/network.go +++ b/token/services/network/fabric/network.go @@ -21,6 +21,7 @@ import ( "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" token2 "github.com/hyperledger-labs/fabric-token-sdk/token" driver3 "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + fabric2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/state/fabric" "github.com/hyperledger-labs/fabric-token-sdk/token/services/logging" common2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/network/common" "github.com/hyperledger-labs/fabric-token-sdk/token/services/network/common/rws/translator" @@ -454,6 +455,14 @@ func (n *Network) ProcessNamespace(namespace string) error { return nil } +func (n *Network) InteropURL(namespace string) string { + return fabric2.FabricURL(token2.TMSID{ + Network: n.Name(), + Channel: n.Channel(), + Namespace: namespace, + }) +} + type FinalityListener struct { net *Network root driver.FinalityListener diff --git a/token/services/network/fabric/tcc/tcc.go b/token/services/network/fabric/tcc/tcc.go index 7d84e742a..23f302030 100644 --- a/token/services/network/fabric/tcc/tcc.go +++ b/token/services/network/fabric/tcc/tcc.go @@ -14,6 +14,7 @@ import ( "os" "runtime/debug" "sync" + "time" "github.com/hyperledger-labs/fabric-token-sdk/token" "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" @@ -35,6 +36,10 @@ const ( AreTokensSpent = "areTokensSpent" PublicParamsPathVarEnv = "PUBLIC_PARAMS_FILE_PATH" + + ProofOfTokenExistenceQuery = "proof_of_token_existence" + ProofOfTokenNonExistenceQuery = "proof_of_token_non_existence" + ProofOfTokenMetadataExistenceQuery = "proof_of_token_metadata_existence" ) type Agent interface { @@ -61,6 +66,17 @@ type PublicParameters interface { GraphHiding() bool } +type ProofOfTokenNonExistenceRequest struct { + TokenID *token2.ID + OriginNetwork string + Deadline time.Time +} + +type ProofOfTokenMetadataExistenceRequest struct { + TokenID *token2.ID + OriginNetwork string +} + type TokenChaincode struct { initOnce sync.Once Validator Validator @@ -134,6 +150,21 @@ func (cc *TokenChaincode) Invoke(stub shim.ChaincodeStubInterface) (res pb.Respo return shim.Error("request to check if tokens are spent is empty") } return cc.AreTokensSpent(args[1], stub) + case ProofOfTokenExistenceQuery: + if len(args) != 2 { + return shim.Error(fmt.Sprintf("(ProofOfTokenExistenceQuery) invalid number of arguments, expected 2, got [%d]", len(args))) + } + return cc.ProofOfTokenExistenceQuery(args[1], stub) + case ProofOfTokenNonExistenceQuery: + if len(args) != 2 { + return shim.Error(fmt.Sprintf("(ProofOfTokenNonExistenceQuery) invalid number of arguments, expected 2, got [%d]", len(args))) + } + return cc.ProofOfTokenNonExistenceQuery(args[1], stub) + case ProofOfTokenMetadataExistenceQuery: + if len(args) != 2 { + return shim.Error(fmt.Sprintf("(ProofOfTokenMetadataExistenceQuery) invalid number of arguments, expected 2, got [%d]", len(args))) + } + return cc.ProofOfTokenMetadataExistenceQuery(args[1], stub) default: return shim.Error(fmt.Sprintf("function [%s] not recognized", f)) } @@ -318,6 +349,76 @@ func (cc *TokenChaincode) AreTokensSpent(idsRaw []byte, stub shim.ChaincodeStubI return shim.Success(raw) } +func (cc *TokenChaincode) ProofOfTokenExistenceQuery(idRaw []byte, stub shim.ChaincodeStubInterface) pb.Response { + raw, err := base64.StdEncoding.DecodeString(string(idRaw)) + if err != nil { + return shim.Error(fmt.Sprintf("(ProofOfTokenExistenceQuery) invalid argument [%s]: failed unmarshalling [%s]", string(idRaw), err)) + } + tokenId := &token2.ID{} + if err := json.Unmarshal(raw, tokenId); err != nil { + return shim.Error(fmt.Sprintf("(ProofOfTokenExistenceQuery) invalid argument [%s]: failed unmarshalling [%s]", string(idRaw), err)) + } + return cc.proveTokenExists(tokenId, stub) +} + +func (cc *TokenChaincode) proveTokenExists(tokenId *token2.ID, stub shim.ChaincodeStubInterface) pb.Response { + logger.Infof("proof of existence [%s]", tokenId.String()) + logger.Infof("generate proof of existence...") + w := translator.New(stub.GetTxID(), translator.NewRWSetWrapper(&rwsWrapper{stub: stub}, "", stub.GetTxID()), &keys.Translator{}) + if err := w.ProveTokenExists(tokenId); err != nil { + return shim.Error(fmt.Sprintf("failed to confirm if token with ID [%s] exists", tokenId)) + } + logger.Infof("proof of existence...done.") + return shim.Success(nil) +} + +func (cc *TokenChaincode) ProofOfTokenNonExistenceQuery(reqRaw []byte, stub shim.ChaincodeStubInterface) pb.Response { + raw, err := base64.StdEncoding.DecodeString(string(reqRaw)) + if err != nil { + return shim.Error(fmt.Sprintf("(ProofOfTokenNonExistenceQuery) invalid argument [%s]: failed unmarshalling [%s]", string(reqRaw), err)) + } + request := &ProofOfTokenNonExistenceRequest{} + if err := json.Unmarshal(raw, request); err != nil { + return shim.Error(fmt.Sprintf("(ProofOfTokenNonExistenceQuery) invalid argument [%s]: failed unmarshalling [%s]", string(reqRaw), err)) + } + return cc.proveTokenDoesNotExist(request.TokenID, request.OriginNetwork, request.Deadline, stub) +} + +func (cc *TokenChaincode) proveTokenDoesNotExist(tokenID *token2.ID, origin string, deadline time.Time, stub shim.ChaincodeStubInterface) pb.Response { + logger.Infof("proof of non existence of token [%s] from network [%s]", tokenID.String(), origin) + logger.Infof("generate proof of non-existence...") + w := translator.New(stub.GetTxID(), translator.NewRWSetWrapper(&rwsWrapper{stub: stub}, "", stub.GetTxID()), &keys.Translator{}) + if err := w.ProveTokenDoesNotExist(tokenID, origin, deadline); err != nil { + return shim.Error(fmt.Sprintf("failed to confirm if token from network [%s] and with key [%s] does not exist", origin, tokenID.String())) + } + logger.Infof("proof of non existence...done.") + return shim.Success(nil) +} + +func (cc *TokenChaincode) ProofOfTokenMetadataExistenceQuery(reqRaw []byte, stub shim.ChaincodeStubInterface) pb.Response { + raw, err := base64.StdEncoding.DecodeString(string(reqRaw)) + if err != nil { + return shim.Error(fmt.Sprintf("(ProofOfTokenMetadataExistenceQuery) invalid argument [%s]: failed unmarshalling [%s]", string(reqRaw), err)) + } + request := &ProofOfTokenMetadataExistenceRequest{} + if err := json.Unmarshal(raw, request); err != nil { + return shim.Error(fmt.Sprintf("(ProofOfTokenMetadataExistenceQuery) invalid argument [%s]: failed unmarshalling [%s]", string(reqRaw), err)) + } + return cc.proveTokenWithMetadataExist(request.TokenID, request.OriginNetwork, stub) +} + +func (cc *TokenChaincode) proveTokenWithMetadataExist(tokenID *token2.ID, origin string, stub shim.ChaincodeStubInterface) pb.Response { + logger.Infof("proof of existence of token with metadata [%s] and network [%s]", tokenID.String(), origin) + logger.Infof("generate proof of existence...") + w := translator.New(stub.GetTxID(), translator.NewRWSetWrapper(&rwsWrapper{stub: stub}, "", stub.GetTxID()), &keys.Translator{}) + if err := w.ProveTokenWithMetadataExists(tokenID, origin); err != nil { + fmt.Println(err.Error()) + return shim.Error(fmt.Sprintf("failed to confirm if token from network [%s] and with key [%s] exist", origin, tokenID.String())) + } + logger.Infof("proof of non existence...done.") + return shim.Success(nil) +} + type ledger struct { stub shim.ChaincodeStubInterface keyTranslator translator.KeyTranslator diff --git a/token/services/network/network.go b/token/services/network/network.go index a1f5fd7a5..115b5fdce 100644 --- a/token/services/network/network.go +++ b/token/services/network/network.go @@ -393,6 +393,14 @@ func (n *Network) ProcessNamespace(namespace string) error { return n.n.ProcessNamespace(namespace) } +func (n *Network) InteropURL(namespace string) string { + interoperability, ok := n.n.(driver.Interoperability) + if !ok { + panic("interoperability not supported") + } + return interoperability.InteropURL(namespace) +} + func (n *Network) Normalize(opt *token.ServiceOptions) (*token.ServiceOptions, error) { return n.n.Normalize(opt) } @@ -456,7 +464,7 @@ func (np *Provider) newNetwork(network string, channel string) (*Network, error) logger.Debugf("new network [%s:%s]", network, channel) return &Network{n: nw}, nil } - return nil, errors.Errorf("no network driver found for [%s:%s], errs [%v]", network, channel, errs) + return nil, errors.Errorf("no network driver found for [%s:%s] among [%d] available, errs [%v]", network, channel, len(np.drivers), errs) } func (np *Provider) Normalize(opt *token.ServiceOptions) (*token.ServiceOptions, error) { diff --git a/token/services/ttx/finality.go b/token/services/ttx/finality.go index d9fd45a3b..e15ed8a16 100644 --- a/token/services/ttx/finality.go +++ b/token/services/ttx/finality.go @@ -49,7 +49,7 @@ func NewFinalityWithOpts(opts ...TxOption) *finalityView { // If the transaction is final, the vault is updated. func (f *finalityView) Call(ctx view.Context) (interface{}, error) { // Compile options - options, err := compile(f.opts...) + options, err := CompileTXOptions(f.opts...) if err != nil { return nil, errors.Wrapf(err, "failed to compile options") } diff --git a/token/services/ttx/opts.go b/token/services/ttx/opts.go index 67c974cfd..1bac15b01 100644 --- a/token/services/ttx/opts.go +++ b/token/services/ttx/opts.go @@ -25,7 +25,7 @@ type TxOptions struct { NoCachingRequest bool } -func compile(opts ...TxOption) (*TxOptions, error) { +func CompileTXOptions(opts ...TxOption) (*TxOptions, error) { txOptions := &TxOptions{} for _, opt := range opts { if err := opt(txOptions); err != nil { diff --git a/token/services/ttx/ordering.go b/token/services/ttx/ordering.go index 0b5c55dff..f674393ee 100644 --- a/token/services/ttx/ordering.go +++ b/token/services/ttx/ordering.go @@ -35,7 +35,7 @@ func NewOrderingViewWithOpts(opts ...TxOption) *orderingView { // 1. It broadcasts the token transaction to the proper backend. func (o *orderingView) Call(context view.Context) (interface{}, error) { // Compile options - options, err := compile(o.opts...) + options, err := CompileTXOptions(o.opts...) if err != nil { return nil, errors.Wrapf(err, "failed to compile options") } diff --git a/token/services/ttx/recipients.go b/token/services/ttx/recipients.go index a2853a1c1..c3593ded7 100644 --- a/token/services/ttx/recipients.go +++ b/token/services/ttx/recipients.go @@ -446,7 +446,9 @@ func (s *RespondExchangeRecipientIdentitiesView) Call(context view.Context) (int ts := token.GetManagementService(context, token.WithTMSID(request.TMSID)) other := request.RecipientData.Identity if err := ts.WalletManager().RegisterRecipientIdentity(&RecipientData{ - Identity: other, AuditInfo: request.RecipientData.AuditInfo, TokenMetadata: request.RecipientData.TokenMetadata, + Identity: other, + AuditInfo: request.RecipientData.AuditInfo, + TokenMetadata: request.RecipientData.TokenMetadata, }); err != nil { return nil, err } diff --git a/token/services/ttx/transaction.go b/token/services/ttx/transaction.go index b3cdca4ac..381f844e2 100644 --- a/token/services/ttx/transaction.go +++ b/token/services/ttx/transaction.go @@ -44,7 +44,7 @@ type Transaction struct { // NewAnonymousTransaction returns a new anonymous token transaction customized with the passed opts func NewAnonymousTransaction(context view.Context, opts ...TxOption) (*Transaction, error) { - txOpts, err := compile(opts...) + txOpts, err := CompileTXOptions(opts...) if err != nil { return nil, errors.WithMessage(err, "failed compiling tx options") } @@ -59,7 +59,7 @@ func NewAnonymousTransaction(context view.Context, opts ...TxOption) (*Transacti // A valid signer is a signer that the target network recognizes as so. For example, in case of fabric, the signer must be a valid fabric identity. // If the passed signer is nil, then the default identity is used. func NewTransaction(context view.Context, signer view.Identity, opts ...TxOption) (*Transaction, error) { - txOpts, err := compile(opts...) + txOpts, err := CompileTXOptions(opts...) if err != nil { return nil, errors.WithMessage(err, "failed compiling tx options") } @@ -139,7 +139,7 @@ func NewTransactionFromBytes(context view.Context, raw []byte) (*Transaction, er } func ReceiveTransaction(context view.Context, opts ...TxOption) (*Transaction, error) { - opt, err := compile(opts...) + opt, err := CompileTXOptions(opts...) if err != nil { return nil, errors.WithMessagef(err, "failed to parse options") } diff --git a/token/tms.go b/token/tms.go index 248dad249..d4b55415d 100644 --- a/token/tms.go +++ b/token/tms.go @@ -20,10 +20,7 @@ var logger = logging.MustGetLogger("token-sdk") type TMSID = driver.TMSID // ServiceProvider is used to return instances of a given type -type ServiceProvider interface { - // GetService returns an instance of the given type - GetService(v interface{}) (interface{}, error) -} +type ServiceProvider = driver.ServiceProvider // ServiceOptions is used to configure the service type ServiceOptions struct { diff --git a/token/token/token.go b/token/token/token.go index 94cff6a8b..b4f81b7a7 100644 --- a/token/token/token.go +++ b/token/token/token.go @@ -6,7 +6,9 @@ SPDX-License-Identifier: Apache-2.0 package token -import "fmt" +import ( + "fmt" +) // ID identifies a token as a function of the identifier of the transaction (issue, transfer) // that created it and its index in that transaction