From a3dfc5f3d4b6c14748bb77d8daf5d5068b952a8a Mon Sep 17 00:00:00 2001 From: Gabriel de Quadros Ligneul <8294320+gligneul@users.noreply.github.com> Date: Tue, 12 Dec 2023 11:27:56 -0300 Subject: [PATCH] feat: add rollup HTTP API --- TODO | 3 +- cmd/nonodo/main.go | 2 + go.mod | 14 +- go.sum | 51 +- internal/model/model.go | 6 +- internal/nonodo/anvil.go | 2 +- internal/nonodo/echo.go | 70 +++ internal/nonodo/inputter.go | 14 +- internal/nonodo/nonodo.go | 6 +- internal/opts/opts.go | 2 + internal/rollup/generated.go | 1119 ++++++++++++++++++++++++++++++++++ internal/rollup/oapi.yaml | 6 + internal/rollup/rollup.go | 224 +++++++ 13 files changed, 1497 insertions(+), 22 deletions(-) create mode 100644 internal/nonodo/echo.go create mode 100644 internal/rollup/generated.go create mode 100644 internal/rollup/oapi.yaml create mode 100644 internal/rollup/rollup.go diff --git a/TODO b/TODO index d18da05b..c9d23fe2 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,8 @@ TODO +- Move hardcoded addresses to opts +- Remove cleanup function from anvil (leaky abstraction) - Add unit tests to model -- Add rollups API - Add inspect API - Add graphql API - Add integration tests diff --git a/cmd/nonodo/main.go b/cmd/nonodo/main.go index 07bee811..4ba6bef7 100644 --- a/cmd/nonodo/main.go +++ b/cmd/nonodo/main.go @@ -28,6 +28,8 @@ func init() { "HTTP port used by Anvil") cmd.Flags().BoolVar(&nonodoOpts.AnvilVerbose, "anvil-verbose", nonodoOpts.AnvilVerbose, "If true, prints Anvil's output") + cmd.Flags().IntVar(&nonodoOpts.HttpPort, "http-port", nonodoOpts.HttpPort, + "HTTP port used by nonodo to serve its APIs") } func run(cmd *cobra.Command, args []string) { diff --git a/go.mod b/go.mod index 19822986..b6d51dc4 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,15 @@ go 1.21.1 require ( github.com/cartesi/rollups-node v1.2.1-0.20231208193135-c22d1806e5b3 github.com/ethereum/go-ethereum v1.13.5 + github.com/labstack/echo/v4 v4.11.3 + github.com/oapi-codegen/runtime v1.1.0 github.com/spf13/cobra v1.8.0 ) require ( github.com/Microsoft/go-winio v0.6.1 // indirect github.com/StackExchange/wmi v1.2.1 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/bits-and-blooms/bitset v1.7.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/consensys/bavard v0.1.13 // indirect @@ -22,21 +25,30 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-stack/stack v1.8.1 // indirect - github.com/google/uuid v1.3.1 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/google/uuid v1.4.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/holiman/uint256 v1.2.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/labstack/gommon v0.4.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/supranational/blst v0.3.11 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.13.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index beea6850..74968af9 100644 --- a/go.sum +++ b/go.sum @@ -2,14 +2,18 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= 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.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= @@ -40,6 +44,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0q github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= @@ -67,6 +72,8 @@ github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= @@ -74,8 +81,8 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= @@ -92,20 +99,28 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM= +github.com/labstack/echo/v4 v4.11.3/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 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/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= @@ -117,6 +132,8 @@ github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8oh github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/oapi-codegen/runtime v1.1.0 h1:rJpoNUawn5XTvekgfkvSZr0RqEnoYpFkyvrzfWeFKWM= +github.com/oapi-codegen/runtime v1.1.0/go.mod h1:BeSfBkWWWnAnGdyS+S/GnlbmHKzf8/hwkvelJZDeKA8= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -145,8 +162,12 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 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/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= @@ -161,6 +182,11 @@ github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2n github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= @@ -169,10 +195,17 @@ golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjs golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= @@ -183,13 +216,15 @@ golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= diff --git a/internal/model/model.go b/internal/model/model.go index e3dd560f..6c24477e 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -70,6 +70,7 @@ type AdvanceInput struct { type InspectInput struct { Index int Status CompletionStatus + Payload []byte ProccessedInputCount int Reports []Report Exception []byte @@ -179,8 +180,9 @@ func (m *NonodoModel) AddInspectInput(payload []byte) int { index := len(m.inspects) input := InspectInput{ - Index: index, - Status: CompletionStatusUnprocessed, + Index: index, + Status: CompletionStatusUnprocessed, + Payload: payload, } m.inspects = append(m.inspects, input) log.Printf("nonodo: inspect input added: %+v", input) diff --git a/internal/nonodo/anvil.go b/internal/nonodo/anvil.go index 1a2f5d88..b57ced96 100644 --- a/internal/nonodo/anvil.go +++ b/internal/nonodo/anvil.go @@ -44,7 +44,7 @@ func loadStateFile() (string, func()) { // Start the anvil process in the host machine. // Return a close function that should be called when the program finishes. -func newAnvil(opts opts.NonodoOpts) (supervisor.Service, func()) { +func newAnvilService(opts opts.NonodoOpts) (supervisor.Service, func()) { stateFile, cleanup := loadStateFile() args := []string{ "--port", fmt.Sprint(opts.AnvilPort), diff --git a/internal/nonodo/echo.go b/internal/nonodo/echo.go new file mode 100644 index 00000000..867969ce --- /dev/null +++ b/internal/nonodo/echo.go @@ -0,0 +1,70 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package nonodo + +import ( + "context" + "fmt" + "log" + "net" + "net/http" + + "github.com/gligneul/nonodo/internal/model" + "github.com/gligneul/nonodo/internal/rollup" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" +) + +// The inputter reads inputs from anvil and puts them in the model. +type echoService struct { + ready chan struct{} + port int + model *model.NonodoModel +} + +// Creates a new inputter from opts. +func newEchoService(model *model.NonodoModel, port int) *echoService { + return &echoService{ + ready: make(chan struct{}), + port: port, + model: model, + } +} + +func (s *echoService) Start(ctx context.Context) error { + // setup routes + e := echo.New() + e.Use(middleware.CORS()) + rollup.Register(e, s.model) + + // create server + addr := fmt.Sprintf("127.0.0.1:%d", s.port) + server := http.Server{ + Addr: addr, + Handler: e, + } + ln, err := net.Listen("tcp", addr) + if err != nil { + return err + } + log.Printf("listening on %v", addr) + s.ready <- struct{}{} + + // create goroutine to shutdown server + go func() { + <-ctx.Done() + server.Shutdown(ctx) + }() + + // serve + err = server.Serve(ln) + if err != http.ErrServerClosed { + return err + } + return nil +} + +func (s *echoService) Ready() <-chan struct{} { + return s.ready +} diff --git a/internal/nonodo/inputter.go b/internal/nonodo/inputter.go index d5279b7e..c8d436b8 100644 --- a/internal/nonodo/inputter.go +++ b/internal/nonodo/inputter.go @@ -15,23 +15,23 @@ import ( "github.com/gligneul/nonodo/internal/model" ) -// The inputter reads inputs from anvil and puts them in the model. -type inputter struct { +// The inputterService reads inputs from anvil and puts them in the model. +type inputterService struct { ready chan struct{} model *model.NonodoModel rpcEndpoint string } -// Creates a new inputter from opts. -func newInputter(model *model.NonodoModel, rpcEndpoint string) *inputter { - return &inputter{ +// Creates a new inputterService from opts. +func newInputterService(model *model.NonodoModel, rpcEndpoint string) *inputterService { + return &inputterService{ ready: make(chan struct{}), model: model, rpcEndpoint: rpcEndpoint, } } -func (i *inputter) Start(ctx context.Context) error { +func (i *inputterService) Start(ctx context.Context) error { client, err := ethclient.DialContext(ctx, i.rpcEndpoint) if err != nil { return err @@ -75,6 +75,6 @@ func (i *inputter) Start(ctx context.Context) error { } } -func (i *inputter) Ready() <-chan struct{} { +func (i *inputterService) Ready() <-chan struct{} { return i.ready } diff --git a/internal/nonodo/nonodo.go b/internal/nonodo/nonodo.go index 59f070b4..e95eae54 100644 --- a/internal/nonodo/nonodo.go +++ b/internal/nonodo/nonodo.go @@ -19,13 +19,15 @@ func Run(ctx context.Context, opts opts.NonodoOpts) { services = append(services, supervisor.NewSignalListenerService()) - anvil, cleanup := newAnvil(opts) + anvil, cleanup := newAnvilService(opts) defer cleanup() services = append(services, anvil) model := model.NewNonodoModel() rpcEndpoint := fmt.Sprintf("ws://127.0.0.1:%v", opts.AnvilPort) - services = append(services, newInputter(model, rpcEndpoint)) + services = append(services, newInputterService(model, rpcEndpoint)) + + services = append(services, newEchoService(model, opts.HttpPort)) supervisor.Start(ctx, services) } diff --git a/internal/opts/opts.go b/internal/opts/opts.go index 89c12327..499543c5 100644 --- a/internal/opts/opts.go +++ b/internal/opts/opts.go @@ -12,6 +12,7 @@ type NonodoOpts struct { AnvilPort int AnvilBlockTime int AnvilVerbose bool + HttpPort int } // Create the options struct with default values. @@ -20,5 +21,6 @@ func NewNonodoOpts() NonodoOpts { AnvilPort: EthDefaultPort, AnvilBlockTime: 1, AnvilVerbose: false, + HttpPort: 8080, } } diff --git a/internal/rollup/generated.go b/internal/rollup/generated.go new file mode 100644 index 00000000..93c6d471 --- /dev/null +++ b/internal/rollup/generated.go @@ -0,0 +1,1119 @@ +// Package rollup provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.0.0 DO NOT EDIT. +package rollup + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/labstack/echo/v4" + "github.com/oapi-codegen/runtime" +) + +// Defines values for FinishStatus. +const ( + Accept FinishStatus = "accept" + Reject FinishStatus = "reject" +) + +// Defines values for RollupRequestRequestType. +const ( + AdvanceState RollupRequestRequestType = "advance_state" + InspectState RollupRequestRequestType = "inspect_state" +) + +// Advance defines model for Advance. +type Advance struct { + Metadata Metadata `json:"metadata"` + + // Payload The payload is in the Ethereum hex binary format. + // The first two characters are '0x' followed by pairs of hexadecimal numbers that correspond to one byte. + // For instance, '0xdeadbeef' corresponds to a payload with length 4 and bytes 222, 173, 190, 175. + // An empty payload is represented by the string '0x'. + Payload Payload `json:"payload"` +} + +// Error Detailed error message. +type Error = string + +// Exception defines model for Exception. +type Exception struct { + // Payload The payload is in the Ethereum hex binary format. + // The first two characters are '0x' followed by pairs of hexadecimal numbers that correspond to one byte. + // For instance, '0xdeadbeef' corresponds to a payload with length 4 and bytes 222, 173, 190, 175. + // An empty payload is represented by the string '0x'. + Payload Payload `json:"payload"` +} + +// Finish defines model for Finish. +type Finish struct { + Status FinishStatus `json:"status"` +} + +// FinishStatus defines model for Finish.Status. +type FinishStatus string + +// IndexResponse defines model for IndexResponse. +type IndexResponse struct { + // Index Position in the Merkle tree. + Index uint64 `json:"index"` +} + +// Inspect defines model for Inspect. +type Inspect struct { + // Payload The payload is in the Ethereum hex binary format. + // The first two characters are '0x' followed by pairs of hexadecimal numbers that correspond to one byte. + // For instance, '0xdeadbeef' corresponds to a payload with length 4 and bytes 222, 173, 190, 175. + // An empty payload is represented by the string '0x'. + Payload Payload `json:"payload"` +} + +// Metadata defines model for Metadata. +type Metadata struct { + // BlockNumber Block number when input was posted. + BlockNumber uint64 `json:"block_number"` + + // EpochIndex Deprecated. Always receives 0. + EpochIndex uint64 `json:"epoch_index"` + + // InputIndex Input index starting from genesis. + InputIndex uint64 `json:"input_index"` + + // MsgSender 20-byte address of the account that submitted the input. + MsgSender string `json:"msg_sender"` + + // Timestamp Unix timestamp of block in milliseconds. + Timestamp uint64 `json:"timestamp"` +} + +// Notice defines model for Notice. +type Notice struct { + // Payload The payload is in the Ethereum hex binary format. + // The first two characters are '0x' followed by pairs of hexadecimal numbers that correspond to one byte. + // For instance, '0xdeadbeef' corresponds to a payload with length 4 and bytes 222, 173, 190, 175. + // An empty payload is represented by the string '0x'. + Payload Payload `json:"payload"` +} + +// Payload The payload is in the Ethereum hex binary format. +// The first two characters are '0x' followed by pairs of hexadecimal numbers that correspond to one byte. +// For instance, '0xdeadbeef' corresponds to a payload with length 4 and bytes 222, 173, 190, 175. +// An empty payload is represented by the string '0x'. +type Payload = string + +// Report defines model for Report. +type Report struct { + // Payload The payload is in the Ethereum hex binary format. + // The first two characters are '0x' followed by pairs of hexadecimal numbers that correspond to one byte. + // For instance, '0xdeadbeef' corresponds to a payload with length 4 and bytes 222, 173, 190, 175. + // An empty payload is represented by the string '0x'. + Payload Payload `json:"payload"` +} + +// RollupRequest defines model for RollupRequest. +type RollupRequest struct { + Data RollupRequest_Data `json:"data"` + RequestType RollupRequestRequestType `json:"request_type"` +} + +// RollupRequest_Data defines model for RollupRequest.Data. +type RollupRequest_Data struct { + union json.RawMessage +} + +// RollupRequestRequestType defines model for RollupRequest.RequestType. +type RollupRequestRequestType string + +// Voucher defines model for Voucher. +type Voucher struct { + // Destination 20-byte address of the destination contract for which the payload will be sent. + Destination string `json:"destination"` + + // Payload String in Ethereum hex binary format describing a method call to be executed by the destination contract. + // The first two characters are '0x' followed by pairs of hexadecimal numbers that correspond to one byte. + // For instance, '0xcdcd77c0' corresponds to a payload with length 4 and bytes 205, 205, 119, 192. + // To describe the method call, the payload should consist of a function selector (method identifier) followed + // by its ABI-encoded arguments. + // ref: https://docs.soliditylang.org/en/v0.8.19/abi-spec.html + Payload string `json:"payload"` +} + +// RegisterExceptionJSONRequestBody defines body for RegisterException for application/json ContentType. +type RegisterExceptionJSONRequestBody = Exception + +// FinishJSONRequestBody defines body for Finish for application/json ContentType. +type FinishJSONRequestBody = Finish + +// AddNoticeJSONRequestBody defines body for AddNotice for application/json ContentType. +type AddNoticeJSONRequestBody = Notice + +// AddReportJSONRequestBody defines body for AddReport for application/json ContentType. +type AddReportJSONRequestBody = Report + +// AddVoucherJSONRequestBody defines body for AddVoucher for application/json ContentType. +type AddVoucherJSONRequestBody = Voucher + +// AsAdvance returns the union data inside the RollupRequest_Data as a Advance +func (t RollupRequest_Data) AsAdvance() (Advance, error) { + var body Advance + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromAdvance overwrites any union data inside the RollupRequest_Data as the provided Advance +func (t *RollupRequest_Data) FromAdvance(v Advance) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeAdvance performs a merge with any union data inside the RollupRequest_Data, using the provided Advance +func (t *RollupRequest_Data) MergeAdvance(v Advance) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsInspect returns the union data inside the RollupRequest_Data as a Inspect +func (t RollupRequest_Data) AsInspect() (Inspect, error) { + var body Inspect + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromInspect overwrites any union data inside the RollupRequest_Data as the provided Inspect +func (t *RollupRequest_Data) FromInspect(v Inspect) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeInspect performs a merge with any union data inside the RollupRequest_Data, using the provided Inspect +func (t *RollupRequest_Data) MergeInspect(v Inspect) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +func (t RollupRequest_Data) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *RollupRequest_Data) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // RegisterExceptionWithBody request with any body + RegisterExceptionWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + RegisterException(ctx context.Context, body RegisterExceptionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // FinishWithBody request with any body + FinishWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + Finish(ctx context.Context, body FinishJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // AddNoticeWithBody request with any body + AddNoticeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + AddNotice(ctx context.Context, body AddNoticeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // AddReportWithBody request with any body + AddReportWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + AddReport(ctx context.Context, body AddReportJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // AddVoucherWithBody request with any body + AddVoucherWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + AddVoucher(ctx context.Context, body AddVoucherJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) RegisterExceptionWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRegisterExceptionRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) RegisterException(ctx context.Context, body RegisterExceptionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRegisterExceptionRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) FinishWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewFinishRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) Finish(ctx context.Context, body FinishJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewFinishRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) AddNoticeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAddNoticeRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) AddNotice(ctx context.Context, body AddNoticeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAddNoticeRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) AddReportWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAddReportRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) AddReport(ctx context.Context, body AddReportJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAddReportRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) AddVoucherWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAddVoucherRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) AddVoucher(ctx context.Context, body AddVoucherJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAddVoucherRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewRegisterExceptionRequest calls the generic RegisterException builder with application/json body +func NewRegisterExceptionRequest(server string, body RegisterExceptionJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewRegisterExceptionRequestWithBody(server, "application/json", bodyReader) +} + +// NewRegisterExceptionRequestWithBody generates requests for RegisterException with any type of body +func NewRegisterExceptionRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/exception") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewFinishRequest calls the generic Finish builder with application/json body +func NewFinishRequest(server string, body FinishJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewFinishRequestWithBody(server, "application/json", bodyReader) +} + +// NewFinishRequestWithBody generates requests for Finish with any type of body +func NewFinishRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/finish") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewAddNoticeRequest calls the generic AddNotice builder with application/json body +func NewAddNoticeRequest(server string, body AddNoticeJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewAddNoticeRequestWithBody(server, "application/json", bodyReader) +} + +// NewAddNoticeRequestWithBody generates requests for AddNotice with any type of body +func NewAddNoticeRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/notice") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewAddReportRequest calls the generic AddReport builder with application/json body +func NewAddReportRequest(server string, body AddReportJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewAddReportRequestWithBody(server, "application/json", bodyReader) +} + +// NewAddReportRequestWithBody generates requests for AddReport with any type of body +func NewAddReportRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/report") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewAddVoucherRequest calls the generic AddVoucher builder with application/json body +func NewAddVoucherRequest(server string, body AddVoucherJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewAddVoucherRequestWithBody(server, "application/json", bodyReader) +} + +// NewAddVoucherRequestWithBody generates requests for AddVoucher with any type of body +func NewAddVoucherRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/voucher") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // RegisterExceptionWithBodyWithResponse request with any body + RegisterExceptionWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterExceptionResponse, error) + + RegisterExceptionWithResponse(ctx context.Context, body RegisterExceptionJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterExceptionResponse, error) + + // FinishWithBodyWithResponse request with any body + FinishWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*FinishResponse, error) + + FinishWithResponse(ctx context.Context, body FinishJSONRequestBody, reqEditors ...RequestEditorFn) (*FinishResponse, error) + + // AddNoticeWithBodyWithResponse request with any body + AddNoticeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AddNoticeResponse, error) + + AddNoticeWithResponse(ctx context.Context, body AddNoticeJSONRequestBody, reqEditors ...RequestEditorFn) (*AddNoticeResponse, error) + + // AddReportWithBodyWithResponse request with any body + AddReportWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AddReportResponse, error) + + AddReportWithResponse(ctx context.Context, body AddReportJSONRequestBody, reqEditors ...RequestEditorFn) (*AddReportResponse, error) + + // AddVoucherWithBodyWithResponse request with any body + AddVoucherWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AddVoucherResponse, error) + + AddVoucherWithResponse(ctx context.Context, body AddVoucherJSONRequestBody, reqEditors ...RequestEditorFn) (*AddVoucherResponse, error) +} + +type RegisterExceptionResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r RegisterExceptionResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r RegisterExceptionResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type FinishResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *RollupRequest +} + +// Status returns HTTPResponse.Status +func (r FinishResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r FinishResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type AddNoticeResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *IndexResponse +} + +// Status returns HTTPResponse.Status +func (r AddNoticeResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r AddNoticeResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type AddReportResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r AddReportResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r AddReportResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type AddVoucherResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *IndexResponse +} + +// Status returns HTTPResponse.Status +func (r AddVoucherResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r AddVoucherResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// RegisterExceptionWithBodyWithResponse request with arbitrary body returning *RegisterExceptionResponse +func (c *ClientWithResponses) RegisterExceptionWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterExceptionResponse, error) { + rsp, err := c.RegisterExceptionWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseRegisterExceptionResponse(rsp) +} + +func (c *ClientWithResponses) RegisterExceptionWithResponse(ctx context.Context, body RegisterExceptionJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterExceptionResponse, error) { + rsp, err := c.RegisterException(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseRegisterExceptionResponse(rsp) +} + +// FinishWithBodyWithResponse request with arbitrary body returning *FinishResponse +func (c *ClientWithResponses) FinishWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*FinishResponse, error) { + rsp, err := c.FinishWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseFinishResponse(rsp) +} + +func (c *ClientWithResponses) FinishWithResponse(ctx context.Context, body FinishJSONRequestBody, reqEditors ...RequestEditorFn) (*FinishResponse, error) { + rsp, err := c.Finish(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseFinishResponse(rsp) +} + +// AddNoticeWithBodyWithResponse request with arbitrary body returning *AddNoticeResponse +func (c *ClientWithResponses) AddNoticeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AddNoticeResponse, error) { + rsp, err := c.AddNoticeWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseAddNoticeResponse(rsp) +} + +func (c *ClientWithResponses) AddNoticeWithResponse(ctx context.Context, body AddNoticeJSONRequestBody, reqEditors ...RequestEditorFn) (*AddNoticeResponse, error) { + rsp, err := c.AddNotice(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseAddNoticeResponse(rsp) +} + +// AddReportWithBodyWithResponse request with arbitrary body returning *AddReportResponse +func (c *ClientWithResponses) AddReportWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AddReportResponse, error) { + rsp, err := c.AddReportWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseAddReportResponse(rsp) +} + +func (c *ClientWithResponses) AddReportWithResponse(ctx context.Context, body AddReportJSONRequestBody, reqEditors ...RequestEditorFn) (*AddReportResponse, error) { + rsp, err := c.AddReport(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseAddReportResponse(rsp) +} + +// AddVoucherWithBodyWithResponse request with arbitrary body returning *AddVoucherResponse +func (c *ClientWithResponses) AddVoucherWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AddVoucherResponse, error) { + rsp, err := c.AddVoucherWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseAddVoucherResponse(rsp) +} + +func (c *ClientWithResponses) AddVoucherWithResponse(ctx context.Context, body AddVoucherJSONRequestBody, reqEditors ...RequestEditorFn) (*AddVoucherResponse, error) { + rsp, err := c.AddVoucher(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseAddVoucherResponse(rsp) +} + +// ParseRegisterExceptionResponse parses an HTTP response from a RegisterExceptionWithResponse call +func ParseRegisterExceptionResponse(rsp *http.Response) (*RegisterExceptionResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RegisterExceptionResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseFinishResponse parses an HTTP response from a FinishWithResponse call +func ParseFinishResponse(rsp *http.Response) (*FinishResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &FinishResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest RollupRequest + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseAddNoticeResponse parses an HTTP response from a AddNoticeWithResponse call +func ParseAddNoticeResponse(rsp *http.Response) (*AddNoticeResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &AddNoticeResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest IndexResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseAddReportResponse parses an HTTP response from a AddReportWithResponse call +func ParseAddReportResponse(rsp *http.Response) (*AddReportResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &AddReportResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseAddVoucherResponse parses an HTTP response from a AddVoucherWithResponse call +func ParseAddVoucherResponse(rsp *http.Response) (*AddVoucherResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &AddVoucherResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest IndexResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Register an exception + // (POST /exception) + RegisterException(ctx echo.Context) error + // Finish and get next request + // (POST /finish) + Finish(ctx echo.Context) error + // Add a new notice + // (POST /notice) + AddNotice(ctx echo.Context) error + // Add a new report + // (POST /report) + AddReport(ctx echo.Context) error + // Add a new voucher + // (POST /voucher) + AddVoucher(ctx echo.Context) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// RegisterException converts echo context to params. +func (w *ServerInterfaceWrapper) RegisterException(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.RegisterException(ctx) + return err +} + +// Finish converts echo context to params. +func (w *ServerInterfaceWrapper) Finish(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.Finish(ctx) + return err +} + +// AddNotice converts echo context to params. +func (w *ServerInterfaceWrapper) AddNotice(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.AddNotice(ctx) + return err +} + +// AddReport converts echo context to params. +func (w *ServerInterfaceWrapper) AddReport(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.AddReport(ctx) + return err +} + +// AddVoucher converts echo context to params. +func (w *ServerInterfaceWrapper) AddVoucher(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.AddVoucher(ctx) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.POST(baseURL+"/exception", wrapper.RegisterException) + router.POST(baseURL+"/finish", wrapper.Finish) + router.POST(baseURL+"/notice", wrapper.AddNotice) + router.POST(baseURL+"/report", wrapper.AddReport) + router.POST(baseURL+"/voucher", wrapper.AddVoucher) + +} diff --git a/internal/rollup/oapi.yaml b/internal/rollup/oapi.yaml new file mode 100644 index 00000000..deb2463a --- /dev/null +++ b/internal/rollup/oapi.yaml @@ -0,0 +1,6 @@ +package: rollup +generate: + echo-server: true + client: true + models: true +output: generated.go diff --git a/internal/rollup/rollup.go b/internal/rollup/rollup.go new file mode 100644 index 00000000..19a8629a --- /dev/null +++ b/internal/rollup/rollup.go @@ -0,0 +1,224 @@ +// Copyright (c) Gabriel de Quadros Ligneul +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +// This package contains the bindings for the rollup OpenAPI spec. +package rollup + +//go:generate oapi-codegen -config=oapi.yaml ../../api/rollup.yaml + +import ( + "log" + "net/http" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/gligneul/nonodo/internal/model" + "github.com/labstack/echo/v4" +) + +const FinishRetries = 50 +const FinishPollInterval = time.Millisecond * 100 + +// Register the rollup API to echo +func Register(e *echo.Echo, model *model.NonodoModel) { + rollupAPI := &rollupAPI{model} + RegisterHandlersWithBaseURL(e, rollupAPI, "rollup") + log.Print("added /rollup to HTTP server") +} + +// Shared struct for request handlers. +type rollupAPI struct { + model *model.NonodoModel +} + +// Handle requests to /finish. +func (r *rollupAPI) Finish(c echo.Context) error { + if !checkContentType(c) { + return c.String(http.StatusUnsupportedMediaType, "invalid content type") + } + + // parse body + var request FinishJSONRequestBody + if err := c.Bind(&request); err != nil { + return err + } + + // validate fields + var accepted bool + switch request.Status { + case Accept: + accepted = true + case Reject: + accepted = false + default: + return c.String(http.StatusBadRequest, "invalid value for status") + } + + // talk to model + for i := 0; i < FinishRetries; i++ { + input := r.model.FinishAndGetNext(accepted) + if input != nil { + resp := convertInput(input) + return c.JSON(http.StatusOK, &resp) + } + ctx := c.Request().Context() + select { + case <-ctx.Done(): + return c.String(http.StatusInternalServerError, ctx.Err().Error()) + case <-time.After(FinishPollInterval): + } + } + return c.String(http.StatusAccepted, "no rollup request available") +} + +// Handle requests to /voucher. +func (r *rollupAPI) AddVoucher(c echo.Context) error { + if !checkContentType(c) { + return c.String(http.StatusUnsupportedMediaType, "invalid content type") + } + + // parse body + var request AddVoucherJSONRequestBody + if err := c.Bind(&request); err != nil { + return err + } + + // validate fields + destination, err := hexutil.Decode(request.Destination) + if err != nil { + return c.String(http.StatusBadRequest, "invalid hex payload") + } + if len(destination) != common.AddressLength { + return c.String(http.StatusBadRequest, "invalid address length") + } + payload, err := hexutil.Decode(request.Payload) + if err != nil { + return c.String(http.StatusBadRequest, "invalid hex payload") + } + + // talk to model + index, err := r.model.AddVoucher(common.Address(destination), payload) + if err != nil { + return c.String(http.StatusForbidden, err.Error()) + } + resp := IndexResponse{ + Index: uint64(index), + } + return c.JSON(http.StatusOK, &resp) +} + +// Handle requests to /notice. +func (r *rollupAPI) AddNotice(c echo.Context) error { + if !checkContentType(c) { + return c.String(http.StatusUnsupportedMediaType, "invalid content type") + } + + // parse body + var request AddNoticeJSONRequestBody + if err := c.Bind(&request); err != nil { + return err + } + + // validate fields + payload, err := hexutil.Decode(request.Payload) + if err != nil { + return c.String(http.StatusBadRequest, "invalid hex payload") + } + + // talk to model + index, err := r.model.AddNotice(payload) + if err != nil { + return c.String(http.StatusForbidden, err.Error()) + } + resp := IndexResponse{ + Index: uint64(index), + } + return c.JSON(http.StatusOK, &resp) +} + +// Handle requests to /report. +func (r *rollupAPI) AddReport(c echo.Context) error { + if !checkContentType(c) { + return c.String(http.StatusUnsupportedMediaType, "invalid content type") + } + + // parse body + var request AddReportJSONRequestBody + if err := c.Bind(&request); err != nil { + return err + } + + // validate fields + payload, err := hexutil.Decode(request.Payload) + if err != nil { + return c.String(http.StatusBadRequest, "invalid hex payload") + } + + // talk to model + err = r.model.AddReport(payload) + if err != nil { + return c.String(http.StatusForbidden, err.Error()) + } + return c.NoContent(http.StatusOK) +} + +// Handle requests to /exception. +func (r *rollupAPI) RegisterException(c echo.Context) error { + if !checkContentType(c) { + return c.String(http.StatusUnsupportedMediaType, "invalid content type") + } + + // parse body + var request RegisterExceptionJSONRequestBody + if err := c.Bind(&request); err != nil { + return err + } + + // validate fields + payload, err := hexutil.Decode(request.Payload) + if err != nil { + return c.String(http.StatusBadRequest, "invalid hex payload") + } + + // talk to model + err = r.model.RaiseException(payload) + if err != nil { + return c.String(http.StatusForbidden, err.Error()) + } + return c.NoContent(http.StatusOK) +} + +// Check whether the content type is application/json. +func checkContentType(c echo.Context) bool { + contentType := c.Request().Header["Content-Type"] + return len(contentType) == 1 && contentType[0] == "application/json" +} + +// Convert model input to API type. +func convertInput(input model.Input) RollupRequest { + var resp RollupRequest + switch input := input.(type) { + case model.AdvanceInput: + advance := Advance{ + Metadata: Metadata{ + BlockNumber: input.BlockNumber, + InputIndex: uint64(input.Index), + MsgSender: hexutil.Encode(input.MsgSender[:]), + Timestamp: uint64(input.Timestamp.Unix()), + }, + Payload: hexutil.Encode(input.Payload), + } + resp.Data.FromAdvance(advance) + resp.RequestType = AdvanceState + case model.InspectInput: + inspect := Inspect{ + Payload: hexutil.Encode(input.Payload), + } + resp.Data.FromInspect(inspect) + resp.RequestType = InspectState + default: + panic("invalid input from model") + } + return resp +}