diff --git a/go.mod b/go.mod index 43e74ba9..a6ef0b0b 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/ipfs/go-ipfs-blockstore v1.0.1 github.com/ipfs/go-ipfs-ds-help v1.0.0 github.com/ipfs/go-ipld-format v0.2.0 + github.com/jinzhu/now v1.1.5 // indirect github.com/jmoiron/sqlx v1.3.5 github.com/joho/godotenv v1.4.0 github.com/lib/pq v1.10.5 @@ -22,9 +23,13 @@ require ( github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.4.0 github.com/spf13/viper v1.11.0 + github.com/thoas/go-funk v0.9.2 // indirect github.com/vulcanize/eth-ipfs-state-validator/v3 v3.0.2 github.com/vulcanize/gap-filler v0.3.1 github.com/vulcanize/ipfs-ethdb/v3 v3.0.3 + golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect + gorm.io/driver/postgres v1.3.7 + gorm.io/gorm v1.23.5 ) require ( @@ -123,18 +128,19 @@ require ( github.com/ipld/go-codec-dagpb v1.3.0 // indirect github.com/ipld/go-ipld-prime v0.12.2 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.10.0 // indirect + github.com/jackc/pgconn v1.12.1 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.1.1 // indirect + github.com/jackc/pgproto3/v2 v2.3.0 // indirect github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.8.1 // indirect - github.com/jackc/pgx/v4 v4.13.0 // indirect - github.com/jackc/puddle v1.1.3 // indirect + github.com/jackc/pgtype v1.11.0 // indirect + github.com/jackc/pgx/v4 v4.16.1 // indirect + github.com/jackc/puddle v1.2.1 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/goprocess v0.1.4 // indirect github.com/jinzhu/copier v0.2.4 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect github.com/klauspost/compress v1.11.7 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/koron/go-ssdp v0.0.2 // indirect @@ -243,7 +249,6 @@ require ( github.com/stretchr/testify v1.7.1 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect - github.com/thoas/go-funk v0.9.2 // indirect github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tklauser/numcpus v0.2.2 // indirect github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef // indirect @@ -265,7 +270,6 @@ require ( go.uber.org/multierr v1.7.0 // indirect go.uber.org/zap v1.19.0 // indirect go4.org v0.0.0-20200411211856-f5505b9728dd // indirect - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect diff --git a/go.sum b/go.sum index 8df04b33..eb2c8782 100644 --- a/go.sum +++ b/go.sum @@ -76,6 +76,7 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= @@ -788,8 +789,9 @@ github.com/jackc/pgconn v1.7.0/go.mod h1:sF/lPpNEMEOp+IYhyQGdAvrG20gWf6A1tKlr0v7 github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.10.0 h1:4EYhlDVEMsJ30nNj0mmgwIUXoq7e9sMJrVC2ED6QlCU= github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8= +github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -807,8 +809,9 @@ github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwX github.com/jackc/pgproto3/v2 v2.0.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.0.5/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y= +github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= @@ -821,8 +824,9 @@ github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkAL github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= github.com/jackc/pgtype v1.4.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs= github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs= +github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= @@ -833,15 +837,17 @@ github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6 github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= github.com/jackc/pgx/v4 v4.8.1/go.mod h1:4HOLxrl8wToZJReD04/yB20GDwf4KBYETvlHciCnwW0= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570= github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0= +github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y= +github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.2/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3 h1:JnPg/5Q9xVJGfjsO5CPUOjnJps1JaRUm8I9FXVCFK94= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw= +github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= @@ -866,8 +872,12 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS github.com/jinzhu/copier v0.2.4 h1:dT3tI+8GzU8DjJFCj9mLYtjfRtUmK7edauduQdcZCpI= github.com/jinzhu/copier v0.2.4/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro= github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= @@ -1920,8 +1930,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2423,6 +2433,11 @@ 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 h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.3.7 h1:FKF6sIMDHDEvvMF/XJvbnCl0nu6KSKUaPXevJ4r+VYQ= +gorm.io/driver/postgres v1.3.7/go.mod h1:f02ympjIcgtHEGFMZvdgTxODZ9snAHDb4hXfigBVuNI= +gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.5 h1:TnlF26wScKSvknUC/Rn8t0NLLM22fypYBlvj1+aH6dM= +gorm.io/gorm v1.23.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/eth/cid_retriever.go b/pkg/eth/cid_retriever.go index bcebcde7..7e27342b 100644 --- a/pkg/eth/cid_retriever.go +++ b/pkg/eth/cid_retriever.go @@ -26,6 +26,8 @@ import ( "github.com/jmoiron/sqlx" "github.com/lib/pq" log "github.com/sirupsen/logrus" + "gorm.io/driver/postgres" + "gorm.io/gorm" "github.com/vulcanize/ipld-eth-server/v3/pkg/shared" ) @@ -39,13 +41,74 @@ type Retriever interface { // CIDRetriever satisfies the CIDRetriever interface for ethereum type CIDRetriever struct { - db *sqlx.DB + db *sqlx.DB + gormDB *gorm.DB +} + +type IPLDModelRecord struct { + models.IPLDModel +} + +// TableName overrides the table name used by IPLD +func (IPLDModelRecord) TableName() string { + return "public.blocks" +} + +type HeaderCIDRecord struct { + CID string `gorm:"column:cid"` + BlockHash string `gorm:"primaryKey"` + BlockNumber string + ParentHash string + Timestamp uint64 + StateRoot string + TotalDifficulty string `gorm:"column:td"` + TxRoot string + RctRoot string `gorm:"column:receipt_root"` + UncleRoot string + Bloom []byte + MhKey string + + // gorm doesn't check if foreign key exists in database. + // It is required to eager load relations using preload. + TransactionCIDs []TransactionCIDRecord `gorm:"foreignKey:HeaderID;references:BlockHash"` + IPLD IPLDModelRecord `gorm:"foreignKey:MhKey;references:Key"` +} + +// TableName overrides the table name used by HeaderCIDRecord +func (HeaderCIDRecord) TableName() string { + return "eth.header_cids" +} + +type TransactionCIDRecord struct { + CID string `gorm:"column:cid"` + TxHash string `gorm:"primaryKey"` + HeaderID string `gorm:"column:header_id"` + Index int64 + Src string + Dst string + MhKey string + IPLD IPLDModelRecord `gorm:"foreignKey:MhKey;references:Key"` +} + +// TableName overrides the table name used by TransactionCIDRecord +func (TransactionCIDRecord) TableName() string { + return "eth.transaction_cids" } // NewCIDRetriever returns a pointer to a new CIDRetriever which supports the CIDRetriever interface func NewCIDRetriever(db *sqlx.DB) *CIDRetriever { + gormDB, err := gorm.Open(postgres.New(postgres.Config{ + Conn: db, + }), &gorm.Config{}) + + if err != nil { + log.Error(err) + return nil + } + return &CIDRetriever{ - db: db, + db: db, + gormDB: gormDB, } } @@ -168,7 +231,7 @@ func (ecr *CIDRetriever) RetrieveHeaderCIDs(tx *sqlx.Tx, blockNumber int64) ([]m log.Debug("retrieving header cids for block ", blockNumber) headers := make([]models.HeaderModel, 0) pgStr := `SELECT CAST(block_number as Text), block_hash,parent_hash,cid,mh_key,CAST(td as Text),node_id, - CAST(reward as Text), state_root,uncle_root,tx_root,receipt_root,bloom,timestamp,times_validated, + CAST(reward as Text), state_root, uncle_root, tx_root, receipt_root, bloom, timestamp, times_validated, coinbase FROM eth.header_cids WHERE block_number = $1` return headers, tx.Select(&headers, pgStr, blockNumber) @@ -304,8 +367,8 @@ func (ecr *CIDRetriever) RetrieveRctCIDsByHeaderID(tx *sqlx.Tx, rctFilter Receip pgStr, args = receiptFilterConditions(&id, pgStr, args, rctFilter, trxHashes) pgStr += ` ORDER BY transaction_cids.index` - receiptCids := make([]models.ReceiptModel, 0) - return receiptCids, tx.Select(&receiptCids, pgStr, args...) + receiptCIDs := make([]models.ReceiptModel, 0) + return receiptCIDs, tx.Select(&receiptCIDs, pgStr, args...) } // RetrieveFilteredGQLLogs retrieves and returns all the log cIDs provided blockHash that conform to the provided @@ -399,8 +462,8 @@ func (ecr *CIDRetriever) RetrieveRctCIDs(tx *sqlx.Tx, rctFilter ReceiptFilter, b pgStr, args = receiptFilterConditions(&id, pgStr, args, rctFilter, txHashes) pgStr += ` ORDER BY transaction_cids.index` - receiptCids := make([]models.ReceiptModel, 0) - return receiptCids, tx.Select(&receiptCids, pgStr, args...) + receiptCIDs := make([]models.ReceiptModel, 0) + return receiptCIDs, tx.Select(&receiptCIDs, pgStr, args...) } func hasTopics(topics [][]string) bool { @@ -576,7 +639,8 @@ func (ecr *CIDRetriever) RetrieveBlockByNumber(blockNumber int64) (models.Header // RetrieveHeaderCIDByHash returns the header for the given block hash func (ecr *CIDRetriever) RetrieveHeaderCIDByHash(tx *sqlx.Tx, blockHash common.Hash) (models.HeaderModel, error) { log.Debug("retrieving header cids for block hash ", blockHash.String()) - pgStr := `SELECT block_hash,cid,mh_key FROM eth.header_cids + pgStr := `SELECT block_hash, CAST(block_number as Text), parent_hash, cid, mh_key, CAST(td as Text), + state_root, uncle_root, tx_root, receipt_root, bloom, timestamp FROM eth.header_cids WHERE block_hash = $1` var headerCID models.HeaderModel return headerCID, tx.Get(&headerCID, pgStr, blockHash.String()) @@ -604,3 +668,58 @@ func (ecr *CIDRetriever) RetrieveReceiptCIDsByTxIDs(tx *sqlx.Tx, txHashes []stri var rctCIDs []models.ReceiptModel return rctCIDs, tx.Select(&rctCIDs, pgStr, pq.Array(txHashes)) } + +// RetrieveHeaderAndTxCIDsByBlockNumber retrieves header CIDs and their associated tx CIDs by block number +func (ecr *CIDRetriever) RetrieveHeaderAndTxCIDsByBlockNumber(blockNumber int64) ([]HeaderCIDRecord, error) { + log.Debug("retrieving header cids and tx cids for block number ", blockNumber) + + var headerCIDs []HeaderCIDRecord + + // https://github.com/go-gorm/gorm/issues/4083#issuecomment-778883283 + // Will use join for TransactionCIDs once preload for 1:N is supported. + err := ecr.gormDB.Preload("TransactionCIDs", func(tx *gorm.DB) *gorm.DB { + return tx.Select("cid", "tx_hash", "index", "src", "dst", "header_id") + }).Joins("IPLD").Find(&headerCIDs, "block_number = ?", blockNumber).Error + + if err != nil { + log.Error("header cid retrieval error") + return nil, err + } + + return headerCIDs, nil +} + +// RetrieveHeaderAndTxCIDsByBlockHash retrieves header CID and their associated tx CIDs by block hash +func (ecr *CIDRetriever) RetrieveHeaderAndTxCIDsByBlockHash(blockHash common.Hash) (HeaderCIDRecord, error) { + log.Debug("retrieving header cid and tx cids for block hash ", blockHash.String()) + + var headerCID HeaderCIDRecord + + // https://github.com/go-gorm/gorm/issues/4083#issuecomment-778883283 + // Will use join for TransactionCIDs once preload for 1:N is supported. + err := ecr.gormDB.Preload("TransactionCIDs", func(tx *gorm.DB) *gorm.DB { + return tx.Select("cid", "tx_hash", "index", "src", "dst", "header_id") + }).Joins("IPLD").First(&headerCID, "block_hash = ?", blockHash.String()).Error + + if err != nil { + log.Error("header cid retrieval error") + return headerCID, err + } + + return headerCID, nil +} + +// RetrieveTxCIDByHash returns the tx for the given tx hash +func (ecr *CIDRetriever) RetrieveTxCIDByHash(txHash string) (TransactionCIDRecord, error) { + log.Debug("retrieving tx cid for tx hash ", txHash) + + var txCID TransactionCIDRecord + + err := ecr.gormDB.Joins("IPLD").First(&txCID, "tx_hash = ?", txHash).Error + if err != nil { + log.Error("header cid retrieval error") + return txCID, err + } + + return txCID, nil +} diff --git a/pkg/eth/ipld_fetcher.go b/pkg/eth/ipld_fetcher.go index 8396d368..751f03f5 100644 --- a/pkg/eth/ipld_fetcher.go +++ b/pkg/eth/ipld_fetcher.go @@ -99,7 +99,7 @@ func (f *IPLDFetcher) Fetch(cids CIDWrapper) (*IPLDs, error) { return iplds, err } -// FetchHeaders fetches headers +// FetchHeader fetches header func (f *IPLDFetcher) FetchHeader(tx *sqlx.Tx, c models.HeaderModel) (models.IPLDModel, error) { log.Debug("fetching header ipld") headerBytes, err := shared.FetchIPLDByMhKey(tx, c.MhKey) diff --git a/pkg/graphql/client.go b/pkg/graphql/client.go index 1040b1bc..096b7a06 100644 --- a/pkg/graphql/client.go +++ b/pkg/graphql/client.go @@ -11,7 +11,7 @@ import ( ) type StorageResponse struct { - Cid string `json:"cid"` + CID string `json:"cid"` Value common.Hash `json:"value"` IpldBlock hexutil.Bytes `json:"ipldBlock"` } @@ -21,14 +21,14 @@ type GetStorageAt struct { } type LogResponse struct { - Topics []common.Hash `json:"topics"` - Data hexutil.Bytes `json:"data"` - Transaction TransactionResp `json:"transaction"` - ReceiptCID string `json:"receiptCID"` - Status int32 `json:"status"` + Topics []common.Hash `json:"topics"` + Data hexutil.Bytes `json:"data"` + Transaction TransactionResponse `json:"transaction"` + ReceiptCID string `json:"receiptCID"` + Status int32 `json:"status"` } -type TransactionResp struct { +type TransactionResponse struct { Hash common.Hash `json:"hash"` } @@ -36,6 +36,52 @@ type GetLogs struct { Responses []LogResponse `json:"getLogs"` } +type IPFSBlockResponse struct { + Key string `json:"key"` + Data string `json:"data"` +} + +type EthTransactionCIDResponse struct { + CID string `json:"cid"` + TxHash string `json:"txHash"` + Index int32 `json:"index"` + Src string `json:"src"` + Dst string `json:"dst"` + BlockByMhKey IPFSBlockResponse `json:"blockByMhKey"` +} + +type EthTransactionCIDByTxHash struct { + Response EthTransactionCIDResponse `json:"ethTransactionCidByTxHash"` +} + +type EthTransactionCIDsByHeaderIdResponse struct { + Nodes []EthTransactionCIDResponse `json:"nodes"` +} + +type EthHeaderCIDResponse struct { + CID string `json:"cid"` + BlockNumber BigInt `json:"blockNumber"` + BlockHash string `json:"blockHash"` + ParentHash string `json:"parentHash"` + Timestamp BigInt `json:"timestamp"` + StateRoot string `json:"stateRoot"` + Td BigInt `json:"td"` + TxRoot string `json:"txRoot"` + ReceiptRoot string `json:"receiptRoot"` + UncleRoot string `json:"uncleRoot"` + Bloom string `json:"bloom"` + EthTransactionCIDsByHeaderId EthTransactionCIDsByHeaderIdResponse `json:"ethTransactionCidsByHeaderId"` + BlockByMhKey IPFSBlockResponse `json:"blockByMhKey"` +} + +type AllEthHeaderCIDsResponse struct { + Nodes []EthHeaderCIDResponse `json:"nodes"` +} + +type AllEthHeaderCIDs struct { + Response AllEthHeaderCIDsResponse `json:"allEthHeaderCids"` +} + type Client struct { client *gqlclient.Client } @@ -117,3 +163,105 @@ func (c *Client) GetStorageAt(ctx context.Context, hash common.Hash, address com } return &storageAt.Response, nil } + +func (c *Client) AllEthHeaderCIDs(ctx context.Context, condition EthHeaderCIDCondition) (*AllEthHeaderCIDsResponse, error) { + var params string + if condition.BlockHash != nil { + params = fmt.Sprintf(`blockHash: "%s"`, *condition.BlockHash) + } + if condition.BlockNumber != nil { + params += fmt.Sprintf(`blockNumber: "%s"`, condition.BlockNumber.String()) + } + + getHeadersQuery := fmt.Sprintf(` + query{ + allEthHeaderCids(condition: { %s }) { + nodes { + cid + blockNumber + blockHash + parentHash + timestamp + stateRoot + td + txRoot + receiptRoot + uncleRoot + bloom + blockByMhKey { + key + data + } + ethTransactionCidsByHeaderId { + nodes { + cid + txHash + index + src + dst + } + } + } + } + } + `, params) + + req := gqlclient.NewRequest(getHeadersQuery) + req.Header.Set("Cache-Control", "no-cache") + + var respData map[string]interface{} + err := c.client.Run(ctx, req, &respData) + if err != nil { + return nil, err + } + + jsonStr, err := json.Marshal(respData) + if err != nil { + return nil, err + } + + var allEthHeaderCIDs AllEthHeaderCIDs + err = json.Unmarshal(jsonStr, &allEthHeaderCIDs) + if err != nil { + return nil, err + } + return &allEthHeaderCIDs.Response, nil +} + +func (c *Client) EthTransactionCIDByTxHash(ctx context.Context, txHash string) (*EthTransactionCIDResponse, error) { + getTxQuery := fmt.Sprintf(` + query{ + ethTransactionCidByTxHash(txHash: "%s") { + cid + txHash + index + src + dst + blockByMhKey { + data + } + } + } + `, txHash) + + req := gqlclient.NewRequest(getTxQuery) + req.Header.Set("Cache-Control", "no-cache") + + var respData map[string]interface{} + err := c.client.Run(ctx, req, &respData) + if err != nil { + return nil, err + } + + jsonStr, err := json.Marshal(respData) + if err != nil { + return nil, err + } + + var ethTxCID EthTransactionCIDByTxHash + err = json.Unmarshal(jsonStr, ðTxCID) + if err != nil { + return nil, err + } + return ðTxCID.Response, nil +} diff --git a/pkg/graphql/graphql.go b/pkg/graphql/graphql.go index 465bf286..853afec9 100644 --- a/pkg/graphql/graphql.go +++ b/pkg/graphql/graphql.go @@ -22,6 +22,7 @@ import ( "context" "database/sql" "errors" + "fmt" "time" "github.com/ethereum/go-ethereum/common" @@ -34,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" "github.com/vulcanize/ipld-eth-server/v3/pkg/eth" + "github.com/vulcanize/ipld-eth-server/v3/pkg/shared" ) var ( @@ -1017,7 +1019,7 @@ func (r *Resolver) GetStorageAt(ctx context.Context, args struct { return nil, err } - if bytes.Compare(rlpValue, eth.EmptyNodeValue) == 0 { + if bytes.Equal(rlpValue, eth.EmptyNodeValue) { return &StorageResult{value: eth.EmptyNodeValue, cid: cid, ipldBlock: ipldBlock}, nil } @@ -1122,3 +1124,244 @@ func decomposeGQLLogs(logCIDs []eth.LogResult) []logsCID { return logs } + +type EthTransactionCID struct { + cid string + txHash string + index int32 + src string + dst string + ipfsBlock IPFSBlock +} + +func (t EthTransactionCID) Cid(ctx context.Context) string { + return t.cid +} + +func (t EthTransactionCID) TxHash(ctx context.Context) string { + return t.txHash +} + +func (t EthTransactionCID) Index(ctx context.Context) int32 { + return t.index +} + +func (t EthTransactionCID) Src(ctx context.Context) string { + return t.src +} + +func (t EthTransactionCID) Dst(ctx context.Context) string { + return t.dst +} + +func (t EthTransactionCID) BlockByMhKey(ctx context.Context) IPFSBlock { + return t.ipfsBlock +} + +type EthTransactionCIDsConnection struct { + nodes []*EthTransactionCID +} + +func (transactionCIDResult EthTransactionCIDsConnection) Nodes(ctx context.Context) []*EthTransactionCID { + return transactionCIDResult.nodes +} + +type IPFSBlock struct { + key string + data string +} + +func (b IPFSBlock) Key(ctx context.Context) string { + return b.key +} + +func (b IPFSBlock) Data(ctx context.Context) string { + return b.data +} + +type EthHeaderCID struct { + cid string + blockNumber BigInt + blockHash string + parentHash string + timestamp BigInt + stateRoot string + td BigInt + txRoot string + receiptRoot string + uncleRoot string + bloom string + transactions []*EthTransactionCID + ipfsBlock IPFSBlock +} + +func (h EthHeaderCID) Cid(ctx context.Context) string { + return h.cid +} + +func (h EthHeaderCID) BlockNumber(ctx context.Context) BigInt { + return h.blockNumber +} + +func (h EthHeaderCID) BlockHash(ctx context.Context) string { + return h.blockHash +} + +func (h EthHeaderCID) ParentHash(ctx context.Context) string { + return h.parentHash +} + +func (h EthHeaderCID) Timestamp(ctx context.Context) BigInt { + return h.timestamp +} + +func (h EthHeaderCID) StateRoot(ctx context.Context) string { + return h.stateRoot +} + +func (h EthHeaderCID) Td(ctx context.Context) BigInt { + return h.td +} + +func (h EthHeaderCID) TxRoot(ctx context.Context) string { + return h.txRoot +} + +func (h EthHeaderCID) ReceiptRoot(ctx context.Context) string { + return h.receiptRoot +} + +func (h EthHeaderCID) UncleRoot(ctx context.Context) string { + return h.uncleRoot +} + +func (h EthHeaderCID) Bloom(ctx context.Context) string { + return h.bloom +} + +func (h EthHeaderCID) EthTransactionCidsByHeaderId(ctx context.Context) EthTransactionCIDsConnection { + return EthTransactionCIDsConnection{nodes: h.transactions} +} + +func (h EthHeaderCID) BlockByMhKey(ctx context.Context) IPFSBlock { + return h.ipfsBlock +} + +type EthHeaderCIDsConnection struct { + nodes []*EthHeaderCID +} + +func (headerCIDResult EthHeaderCIDsConnection) Nodes(ctx context.Context) []*EthHeaderCID { + return headerCIDResult.nodes +} + +type EthHeaderCIDCondition struct { + BlockNumber *BigInt + BlockHash *string +} + +func (r *Resolver) AllEthHeaderCids(ctx context.Context, args struct { + Condition *EthHeaderCIDCondition +}) (*EthHeaderCIDsConnection, error) { + var headerCIDs []eth.HeaderCIDRecord + var err error + if args.Condition.BlockHash != nil { + headerCID, err := r.backend.Retriever.RetrieveHeaderAndTxCIDsByBlockHash(common.HexToHash(*args.Condition.BlockHash)) + if err != nil { + return nil, err + } + + headerCIDs = append(headerCIDs, headerCID) + } else if args.Condition.BlockNumber != nil { + headerCIDs, err = r.backend.Retriever.RetrieveHeaderAndTxCIDsByBlockNumber(args.Condition.BlockNumber.ToInt().Int64()) + if err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("provide block number or block hash") + } + + // Begin tx + tx, err := r.backend.DB.Beginx() + if err != nil { + return nil, err + } + defer func() { + if p := recover(); p != nil { + shared.Rollback(tx) + panic(p) + } else if err != nil { + shared.Rollback(tx) + } else { + err = tx.Commit() + } + }() + + var resultNodes []*EthHeaderCID + for _, headerCID := range headerCIDs { + var blockNumber BigInt + blockNumber.UnmarshalText([]byte(headerCID.BlockNumber)) + + var timestamp BigInt + timestamp.SetUint64(headerCID.Timestamp) + + var td BigInt + td.UnmarshalText([]byte(headerCID.TotalDifficulty)) + + ethHeaderCIDNode := EthHeaderCID{ + cid: headerCID.CID, + blockNumber: blockNumber, + blockHash: headerCID.BlockHash, + parentHash: headerCID.ParentHash, + timestamp: timestamp, + stateRoot: headerCID.StateRoot, + td: td, + txRoot: headerCID.TxRoot, + receiptRoot: headerCID.RctRoot, + uncleRoot: headerCID.UncleRoot, + bloom: Bytes(headerCID.Bloom).String(), + ipfsBlock: IPFSBlock{ + key: headerCID.IPLD.Key, + data: Bytes(headerCID.IPLD.Data).String(), + }, + } + + for _, txCID := range headerCID.TransactionCIDs { + ethHeaderCIDNode.transactions = append(ethHeaderCIDNode.transactions, &EthTransactionCID{ + cid: txCID.CID, + txHash: txCID.TxHash, + index: int32(txCID.Index), + src: txCID.Src, + dst: txCID.Dst, + }) + } + + resultNodes = append(resultNodes, ðHeaderCIDNode) + } + + return &EthHeaderCIDsConnection{ + nodes: resultNodes, + }, nil +} + +func (r *Resolver) EthTransactionCidByTxHash(ctx context.Context, args struct { + TxHash string +}) (*EthTransactionCID, error) { + txCID, err := r.backend.Retriever.RetrieveTxCIDByHash(args.TxHash) + + if err != nil { + return nil, err + } + + return &EthTransactionCID{ + cid: txCID.CID, + txHash: txCID.TxHash, + index: int32(txCID.Index), + src: txCID.Src, + dst: txCID.Dst, + ipfsBlock: IPFSBlock{ + key: txCID.IPLD.Key, + data: Bytes(txCID.IPLD.Data).String(), + }, + }, nil +} diff --git a/pkg/graphql/graphql_test.go b/pkg/graphql/graphql_test.go index 50375b3a..33397572 100644 --- a/pkg/graphql/graphql_test.go +++ b/pkg/graphql/graphql_test.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "math/big" + "strconv" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -180,7 +181,7 @@ var _ = Describe("GraphQL", func() { { Topics: test_helpers.MockLog1.Topics, Data: hexutil.Bytes(test_helpers.MockLog1.Data), - Transaction: graphql.TransactionResp{Hash: test_helpers.MockTransactions[0].Hash()}, + Transaction: graphql.TransactionResponse{Hash: test_helpers.MockTransactions[0].Hash()}, ReceiptCID: test_helpers.Rct1CID.String(), Status: int32(test_helpers.MockReceipts[0].Status), }, @@ -197,7 +198,7 @@ var _ = Describe("GraphQL", func() { { Topics: test_helpers.MockLog6.Topics, Data: hexutil.Bytes(test_helpers.MockLog6.Data), - Transaction: graphql.TransactionResp{Hash: test_helpers.MockTransactions[3].Hash()}, + Transaction: graphql.TransactionResponse{Hash: test_helpers.MockTransactions[3].Hash()}, ReceiptCID: test_helpers.Rct4CID.String(), Status: int32(test_helpers.MockReceipts[3].Status), }, @@ -250,4 +251,84 @@ var _ = Describe("GraphQL", func() { Expect(storageRes.Value).To(Equal(common.Hash{})) }) }) + + Describe("allEthHeaderCids", func() { + It("Retrieves header_cids that matches the provided blockNumber", func() { + allEthHeaderCIDsResp, err := client.AllEthHeaderCIDs(ctx, graphql.EthHeaderCIDCondition{BlockNumber: new(graphql.BigInt).SetUint64(2)}) + Expect(err).ToNot(HaveOccurred()) + + headerCIDs, err := backend.Retriever.RetrieveHeaderAndTxCIDsByBlockNumber(2) + Expect(err).ToNot(HaveOccurred()) + + for idx, headerCID := range headerCIDs { + ethHeaderCID := allEthHeaderCIDsResp.Nodes[idx] + + compareEthHeaderCID(ethHeaderCID, headerCID) + } + }) + + It("Retrieves header_cids that matches the provided blockHash", func() { + blockHash := blocks[1].Hash().String() + allEthHeaderCIDsResp, err := client.AllEthHeaderCIDs(ctx, graphql.EthHeaderCIDCondition{BlockHash: &blockHash}) + Expect(err).ToNot(HaveOccurred()) + + headerCID, err := backend.Retriever.RetrieveHeaderAndTxCIDsByBlockHash(blocks[1].Hash()) + Expect(err).ToNot(HaveOccurred()) + + Expect(len(allEthHeaderCIDsResp.Nodes)).To(Equal(1)) + ethHeaderCID := allEthHeaderCIDsResp.Nodes[0] + compareEthHeaderCID(ethHeaderCID, headerCID) + }) + }) + + Describe("ethTransactionCidByTxHash", func() { + It("Retrieves tx_cid that matches the provided txHash", func() { + txHash := blocks[2].Transactions()[0].Hash().String() + ethTransactionCIDResp, err := client.EthTransactionCIDByTxHash(ctx, txHash) + Expect(err).ToNot(HaveOccurred()) + + txCID, err := backend.Retriever.RetrieveTxCIDByHash(txHash) + Expect(err).ToNot(HaveOccurred()) + + compareEthTxCID(*ethTransactionCIDResp, txCID) + + Expect(ethTransactionCIDResp.BlockByMhKey.Data).To(Equal(graphql.Bytes(txCID.IPLD.Data).String())) + }) + }) }) + +func compareEthHeaderCID(ethHeaderCID graphql.EthHeaderCIDResponse, headerCID eth.HeaderCIDRecord) { + blockNumber, err := strconv.ParseInt(headerCID.BlockNumber, 10, 64) + Expect(err).ToNot(HaveOccurred()) + + td, err := strconv.ParseInt(headerCID.TotalDifficulty, 10, 64) + Expect(err).ToNot(HaveOccurred()) + + Expect(ethHeaderCID.CID).To(Equal(headerCID.CID)) + Expect(ethHeaderCID.BlockNumber).To(Equal(*new(graphql.BigInt).SetUint64(uint64(blockNumber)))) + Expect(ethHeaderCID.BlockHash).To(Equal(headerCID.BlockHash)) + Expect(ethHeaderCID.ParentHash).To(Equal(headerCID.ParentHash)) + Expect(ethHeaderCID.Timestamp).To(Equal(*new(graphql.BigInt).SetUint64(headerCID.Timestamp))) + Expect(ethHeaderCID.StateRoot).To(Equal(headerCID.StateRoot)) + Expect(ethHeaderCID.Td).To(Equal(*new(graphql.BigInt).SetUint64(uint64(td)))) + Expect(ethHeaderCID.TxRoot).To(Equal(headerCID.TxRoot)) + Expect(ethHeaderCID.ReceiptRoot).To(Equal(headerCID.RctRoot)) + Expect(ethHeaderCID.UncleRoot).To(Equal(headerCID.UncleRoot)) + Expect(ethHeaderCID.Bloom).To(Equal(graphql.Bytes(headerCID.Bloom).String())) + + for tIdx, txCID := range headerCID.TransactionCIDs { + ethTxCID := ethHeaderCID.EthTransactionCIDsByHeaderId.Nodes[tIdx] + compareEthTxCID(ethTxCID, txCID) + } + + Expect(ethHeaderCID.BlockByMhKey.Data).To(Equal(graphql.Bytes(headerCID.IPLD.Data).String())) + Expect(ethHeaderCID.BlockByMhKey.Key).To(Equal(headerCID.IPLD.Key)) +} + +func compareEthTxCID(ethTxCID graphql.EthTransactionCIDResponse, txCID eth.TransactionCIDRecord) { + Expect(ethTxCID.CID).To(Equal(txCID.CID)) + Expect(ethTxCID.TxHash).To(Equal(txCID.TxHash)) + Expect(ethTxCID.Index).To(Equal(int32(txCID.Index))) + Expect(ethTxCID.Src).To(Equal(txCID.Src)) + Expect(ethTxCID.Dst).To(Equal(txCID.Dst)) +} diff --git a/pkg/graphql/schema.go b/pkg/graphql/schema.go index b70765ac..eb0d89fc 100644 --- a/pkg/graphql/schema.go +++ b/pkg/graphql/schema.go @@ -25,8 +25,7 @@ const schema string = ` # An empty byte string is represented as '0x'. Byte strings must have an even number of hexadecimal nybbles. scalar Bytes # BigInt is a large integer. Input is accepted as either a JSON number or as a string. - # Strings may be either decimal or 0x-prefixed hexadecimal. Output values are all - # 0x-prefixed hexadecimal. + # Input and output strings may be either decimal or 0x-prefixed hexadecimal depending upon the resolver implementation. scalar BigInt # Long is a 64 bit unsigned integer. scalar Long @@ -138,16 +137,16 @@ const schema string = ` # empty, results will not be filtered by address. addresses: [Address!] # Topics list restricts matches to particular event topics. Each event has a list - # of topics. Topics matches a prefix of that list. An empty element array matches any - # topic. Non-empty elements represent an alternative that matches any of the - # contained topics. - # - # Examples: - # - [] or nil matches any topic list - # - [[A]] matches topic A in first position - # - [[], [B]] matches any topic in first position, B in second position - # - [[A], [B]] matches topic A in first position, B in second position - # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position + # of topics. Topics matches a prefix of that list. An empty element array matches any + # topic. Non-empty elements represent an alternative that matches any of the + # contained topics. + # + # Examples: + # - [] or nil matches any topic list + # - [[A]] matches topic A in first position + # - [[], [B]] matches any topic in first position, B in second position + # - [[A], [B]] matches topic A in first position, B in second position + # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position topics: [[Bytes32!]!] } @@ -258,16 +257,16 @@ const schema string = ` # empty, results will not be filtered by address. addresses: [Address!] # Topics list restricts matches to particular event topics. Each event has a list - # of topics. Topics matches a prefix of that list. An empty element array matches any - # topic. Non-empty elements represent an alternative that matches any of the - # contained topics. - # - # Examples: - # - [] or nil matches any topic list - # - [[A]] matches topic A in first position - # - [[], [B]] matches any topic in first position, B in second position - # - [[A], [B]] matches topic A in first position, B in second position - # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position + # of topics. Topics matches a prefix of that list. An empty element array matches any + # topic. Non-empty elements represent an alternative that matches any of the + # contained topics. + # + # Examples: + # - [] or nil matches any topic list + # - [[A]] matches topic A in first position + # - [[], [B]] matches any topic in first position, B in second position + # - [[A], [B]] matches topic A in first position, B in second position + # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position topics: [[Bytes32!]!] } @@ -282,6 +281,49 @@ const schema string = ` ipldBlock: Bytes! } + input EthHeaderCidCondition { + blockNumber: BigInt + blockHash: String + } + + type EthTransactionCid { + cid: String! + txHash: String! + index: Int! + src: String! + dst: String! + blockByMhKey: IPFSBlock! + } + + type EthTransactionCidsConnection { + nodes: [EthTransactionCid]! + } + + type IPFSBlock { + key: String! + data: String! + } + + type EthHeaderCid { + cid: String! + blockNumber: BigInt! + blockHash: String! + parentHash: String! + timestamp: BigInt! + stateRoot: String! + td: BigInt! + txRoot: String! + receiptRoot: String! + uncleRoot: String! + bloom: String! + ethTransactionCidsByHeaderId: EthTransactionCidsConnection! + blockByMhKey: IPFSBlock! + } + + type EthHeaderCidsConnection { + nodes: [EthHeaderCid]! + } + type Query { # Block fetches an Ethereum block by number or by hash. If neither is # supplied, the most recent known block is returned. @@ -302,5 +344,11 @@ const schema string = ` # Get contract logs by block hash and contract address. getLogs(blockHash: Bytes32!, contract: Address): [Log!] + + # PostGraphile alternative to get headers with transactions using block number or block hash. + allEthHeaderCids(condition: EthHeaderCidCondition): EthHeaderCidsConnection + + # PostGraphile alternative to get transactions using transaction hash. + ethTransactionCidByTxHash(txHash: String!): EthTransactionCid } ` diff --git a/pkg/graphql/types.go b/pkg/graphql/types.go new file mode 100644 index 00000000..7213cecc --- /dev/null +++ b/pkg/graphql/types.go @@ -0,0 +1,122 @@ +// VulcanizeDB +// Copyright © 2022 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package graphql + +import ( + "encoding/hex" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// Bytes marshals as a JSON string with \x prefix. +// The empty slice marshals as "\x". +type Bytes []byte + +// MarshalText implements encoding.TextMarshaler +func (b Bytes) MarshalText() ([]byte, error) { + result := make([]byte, len(b)*2+2) + copy(result, `\x`) + hex.Encode(result[2:], b) + return result, nil +} + +// String returns the hex encoding of b. +func (b Bytes) String() string { + return b.encode() +} + +// Encode encodes b as a hex string with "\x" prefix. +// This is to make the output to be the same as given by postgraphile. +// graphql-go prepends another "\" to the output resulting in prefix "\\x". +func (b Bytes) encode() string { + result := make([]byte, len(b)*2+2) + copy(result, `\x`) + hex.Encode(result[2:], b) + return string(result) +} + +type BigInt big.Int + +// ToInt converts b to a big.Int. +func (b *BigInt) ToInt() *big.Int { + return (*big.Int)(b) +} + +// String returns value of b as a decimal string. +func (b *BigInt) String() string { + return b.ToInt().String() +} + +// SetUint64 sets b to x and returns x. +func (b *BigInt) SetUint64(x uint64) *BigInt { + var val big.Int + val.SetUint64(x) + *b = (BigInt)(val) + return b +} + +// MarshalText implements encoding.TextMarshaler +func (b BigInt) MarshalText() ([]byte, error) { + return []byte(b.String()), nil +} + +// UnmarshalText implements encoding.TextUnmarshaler +func (b *BigInt) UnmarshalText(input []byte) error { + raw, err := checkNumberText(input) + if err != nil { + return err + } + if len(raw) > 64 { + return hexutil.ErrBig256Range + } + + var val big.Int + val.SetString(string(input[:]), 10) + *b = (BigInt)(val) + return nil +} + +// ImplementsGraphQLType returns true if BigInt implements the provided GraphQL type. +func (b BigInt) ImplementsGraphQLType(name string) bool { return name == "BigInt" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (b *BigInt) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + return b.UnmarshalText([]byte(input)) + case int32: + var num big.Int + num.SetInt64(int64(input)) + *b = BigInt(num) + default: + err = fmt.Errorf("unexpected type %T for BigInt", input) + } + return err +} + +func checkNumberText(input []byte) (raw []byte, err error) { + if len(input) == 0 { + return nil, nil // empty strings are allowed + } + if len(input) > 1 && input[0] == '0' { + return nil, hexutil.ErrLeadingZero + } + return input, nil +}