diff --git a/capabilities/.mockery.yaml b/capabilities/.mockery.yaml new file mode 100644 index 0000000..5e9a91b --- /dev/null +++ b/capabilities/.mockery.yaml @@ -0,0 +1,18 @@ +dir: "{{ .InterfaceDir }}/mocks" +mockname: "{{ .InterfaceName }}" +outpkg: mocks +filename: "{{ .InterfaceName | snakecase }}.go" +packages: + github.com/smartcontractkit/chainlink-framework/capabilities/writetarget: + interfaces: + ProductSpecificProcessor: + TargetStrategy: + chainService: + config: + mockname: ChainService + github.com/smartcontractkit/chainlink-common/pkg/beholder: + config: + dir: writetarget/beholder/mocks/ + interfaces: + ProtoEmitter: + ProtoProcessor: diff --git a/capabilities/go.mod b/capabilities/go.mod index 0f5e3ce..3f645ae 100644 --- a/capabilities/go.mod +++ b/capabilities/go.mod @@ -1,18 +1,29 @@ module github.com/smartcontractkit/chainlink-framework/capabilities -go 1.24.1 +go 1.24.2 + +toolchain go1.24.3 require ( github.com/google/uuid v1.6.0 github.com/jpillora/backoff v1.0.0 - github.com/smartcontractkit/chainlink-common v0.6.1-0.20250407100046-dfdf9600557b + github.com/smartcontractkit/chainlink-common v0.7.1-0.20250618162808-a5a42ee8701b github.com/stretchr/testify v1.10.0 go.opentelemetry.io/otel v1.35.0 go.opentelemetry.io/otel/metric v1.35.0 + go.opentelemetry.io/otel/trace v1.35.0 + go.uber.org/zap v1.27.0 + google.golang.org/protobuf v1.36.6 ) require ( + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/buger/jsonparser v1.1.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.15.2 // indirect + github.com/cloudevents/sdk-go/v2 v2.16.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -20,11 +31,28 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.25.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect + github.com/invopop/jsonschema v0.12.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mr-tron/base58 v1.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.21.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.63.0 // indirect + github.com/prometheus/procfs v0.16.0 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/smartcontractkit/libocr v0.0.0-20250328171017-609ec10a5510 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.10.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.10.0 // indirect @@ -40,17 +68,15 @@ require ( go.opentelemetry.io/otel/sdk v1.35.0 // indirect go.opentelemetry.io/otel/sdk/log v0.10.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.36.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/sys v0.31.0 // indirect + golang.org/x/sys v0.32.0 // indirect golang.org/x/text v0.23.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250219182151-9fdb1cabc7b2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect - google.golang.org/grpc v1.71.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + google.golang.org/grpc v1.72.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/capabilities/go.sum b/capabilities/go.sum index 055feed..fe20dd1 100644 --- a/capabilities/go.sum +++ b/capabilities/go.sum @@ -1,5 +1,19 @@ +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +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/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.15.2 h1:FIvfKlS2mcuP0qYY6yzdIU9xdrRd/YMP0bNwFjXd0u8= +github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.15.2/go.mod h1:POsdVp/08Mki0WD9QvvgRRpg9CQ6zhjfRrBoEY8JFS8= +github.com/cloudevents/sdk-go/v2 v2.16.0 h1:wnunjgiLQCfYlyo+E4+mFlZtAh7pKn7vT8MMD3lSwCg= +github.com/cloudevents/sdk-go/v2 v2.16.0/go.mod h1:5YWqklyhDSmGzBK/JENKKXdulbPq0JFf3c/KEnMLqgg= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= @@ -17,36 +31,78 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= +github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= +github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= +github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= +github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= +github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM= +github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/smartcontractkit/chainlink-common v0.6.1-0.20250407100046-dfdf9600557b h1:a89rWexN5nUjJwH3X2ocEWTWEzz4/BDykPSFq2pYHMU= -github.com/smartcontractkit/chainlink-common v0.6.1-0.20250407100046-dfdf9600557b/go.mod h1:ASXpANdCfcKd+LF3Vhz37q4rmJ/XYQKEQ3La1k7idp0= +github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 h1:WCcC4vZDS1tYNxjWlwRJZQy28r8CMoggKnxNzxsVDMQ= +github.com/santhosh-tekuri/jsonschema/v5 v5.2.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/smartcontractkit/chainlink-common v0.7.1-0.20250618162808-a5a42ee8701b h1:nS5njF5W9lY1LnTITt3V2M35dT19JPpuVg6//vlzFiU= +github.com/smartcontractkit/chainlink-common v0.7.1-0.20250618162808-a5a42ee8701b/go.mod h1:1ntZ0rtQpPx6h+xlcOJp0ccqHFaxTzW2Z62FJG358q0= github.com/smartcontractkit/libocr v0.0.0-20250328171017-609ec10a5510 h1:gm8Jli0sdkrZYnrWBngAkPSDzFDkdNCy1/Dj86kVtYk= github.com/smartcontractkit/libocr v0.0.0-20250328171017-609ec10a5510/go.mod h1:Mb7+/LC4edz7HyHxX4QkE42pSuov4AV68+AxBXAap0o= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= @@ -93,18 +149,22 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= google.golang.org/genproto/googleapis/api v0.0.0-20250219182151-9fdb1cabc7b2 h1:35ZFtrCgaAjF7AFAK0+lRSf+4AyYnWRbH7og13p7rZ4= google.golang.org/genproto/googleapis/api v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:W9ynFDP/shebLB1Hl/ESTOap2jHd6pmLXPNZC7SVDbA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= +google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/capabilities/writetarget/README.md b/capabilities/writetarget/README.md new file mode 100644 index 0000000..bde7049 --- /dev/null +++ b/capabilities/writetarget/README.md @@ -0,0 +1,6 @@ +# Generalized Write Target + +This framework allows for any chain to implement the write target with minimal friction simply by implementing the interface [target_strategy](https://github.com/smartcontractkit/chainlink-framework/blob/0647c811e8e34635171517e64571650c59402d6a/capabilities/writetarget/write_target.go#L50-L57) + +[Aptos Implementation](https://github.com/smartcontractkit/chainlink-aptos/blob/133766330253521cb0eb23b8d86ca89e187d5bc2/relayer/write_target/strategy.go#L1-L171) +[EVM Implementation](https://github.com/smartcontractkit/chainlink/blob/12c6f874df3ea9571eb3c8aa98f4a41c4d5b49b2/core/services/relay/evm/target_strategy.go#L1-L226) \ No newline at end of file diff --git a/capabilities/writetarget/beholder/info.go b/capabilities/writetarget/beholder/info.go index 264b6d7..607825a 100644 --- a/capabilities/writetarget/beholder/info.go +++ b/capabilities/writetarget/beholder/info.go @@ -11,6 +11,14 @@ type MetricInfo struct { Description string } +// ChainInfo contains the chain information (used as execution context) +type ChainInfo struct { + FamilyName string + ChainID string + NetworkName string + NetworkNameFull string +} + // NewInt64Counter creates a new Int64Counter metric func (m MetricInfo) NewInt64Counter(meter metric.Meter) (metric.Int64Counter, error) { return meter.Int64Counter( diff --git a/capabilities/writetarget/beholder/mocks/proto_emitter.go b/capabilities/writetarget/beholder/mocks/proto_emitter.go new file mode 100644 index 0000000..e4ca3b8 --- /dev/null +++ b/capabilities/writetarget/beholder/mocks/proto_emitter.go @@ -0,0 +1,153 @@ +// Code generated by mockery v2.53.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" +) + +// ProtoEmitter is an autogenerated mock type for the ProtoEmitter type +type ProtoEmitter struct { + mock.Mock +} + +type ProtoEmitter_Expecter struct { + mock *mock.Mock +} + +func (_m *ProtoEmitter) EXPECT() *ProtoEmitter_Expecter { + return &ProtoEmitter_Expecter{mock: &_m.Mock} +} + +// Emit provides a mock function with given fields: ctx, m, attrKVs +func (_m *ProtoEmitter) Emit(ctx context.Context, m protoreflect.ProtoMessage, attrKVs ...interface{}) error { + var _ca []interface{} + _ca = append(_ca, ctx, m) + _ca = append(_ca, attrKVs...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Emit") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, protoreflect.ProtoMessage, ...interface{}) error); ok { + r0 = rf(ctx, m, attrKVs...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ProtoEmitter_Emit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Emit' +type ProtoEmitter_Emit_Call struct { + *mock.Call +} + +// Emit is a helper method to define mock.On call +// - ctx context.Context +// - m protoreflect.ProtoMessage +// - attrKVs ...interface{} +func (_e *ProtoEmitter_Expecter) Emit(ctx interface{}, m interface{}, attrKVs ...interface{}) *ProtoEmitter_Emit_Call { + return &ProtoEmitter_Emit_Call{Call: _e.mock.On("Emit", + append([]interface{}{ctx, m}, attrKVs...)...)} +} + +func (_c *ProtoEmitter_Emit_Call) Run(run func(ctx context.Context, m protoreflect.ProtoMessage, attrKVs ...interface{})) *ProtoEmitter_Emit_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(protoreflect.ProtoMessage), variadicArgs...) + }) + return _c +} + +func (_c *ProtoEmitter_Emit_Call) Return(_a0 error) *ProtoEmitter_Emit_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProtoEmitter_Emit_Call) RunAndReturn(run func(context.Context, protoreflect.ProtoMessage, ...interface{}) error) *ProtoEmitter_Emit_Call { + _c.Call.Return(run) + return _c +} + +// EmitWithLog provides a mock function with given fields: ctx, m, attrKVs +func (_m *ProtoEmitter) EmitWithLog(ctx context.Context, m protoreflect.ProtoMessage, attrKVs ...interface{}) error { + var _ca []interface{} + _ca = append(_ca, ctx, m) + _ca = append(_ca, attrKVs...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for EmitWithLog") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, protoreflect.ProtoMessage, ...interface{}) error); ok { + r0 = rf(ctx, m, attrKVs...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ProtoEmitter_EmitWithLog_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EmitWithLog' +type ProtoEmitter_EmitWithLog_Call struct { + *mock.Call +} + +// EmitWithLog is a helper method to define mock.On call +// - ctx context.Context +// - m protoreflect.ProtoMessage +// - attrKVs ...interface{} +func (_e *ProtoEmitter_Expecter) EmitWithLog(ctx interface{}, m interface{}, attrKVs ...interface{}) *ProtoEmitter_EmitWithLog_Call { + return &ProtoEmitter_EmitWithLog_Call{Call: _e.mock.On("EmitWithLog", + append([]interface{}{ctx, m}, attrKVs...)...)} +} + +func (_c *ProtoEmitter_EmitWithLog_Call) Run(run func(ctx context.Context, m protoreflect.ProtoMessage, attrKVs ...interface{})) *ProtoEmitter_EmitWithLog_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(protoreflect.ProtoMessage), variadicArgs...) + }) + return _c +} + +func (_c *ProtoEmitter_EmitWithLog_Call) Return(_a0 error) *ProtoEmitter_EmitWithLog_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProtoEmitter_EmitWithLog_Call) RunAndReturn(run func(context.Context, protoreflect.ProtoMessage, ...interface{}) error) *ProtoEmitter_EmitWithLog_Call { + _c.Call.Return(run) + return _c +} + +// NewProtoEmitter creates a new instance of ProtoEmitter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewProtoEmitter(t interface { + mock.TestingT + Cleanup(func()) +}) *ProtoEmitter { + mock := &ProtoEmitter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/capabilities/writetarget/beholder/mocks/proto_processor.go b/capabilities/writetarget/beholder/mocks/proto_processor.go new file mode 100644 index 0000000..e0cc396 --- /dev/null +++ b/capabilities/writetarget/beholder/mocks/proto_processor.go @@ -0,0 +1,95 @@ +// Code generated by mockery v2.53.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" +) + +// ProtoProcessor is an autogenerated mock type for the ProtoProcessor type +type ProtoProcessor struct { + mock.Mock +} + +type ProtoProcessor_Expecter struct { + mock *mock.Mock +} + +func (_m *ProtoProcessor) EXPECT() *ProtoProcessor_Expecter { + return &ProtoProcessor_Expecter{mock: &_m.Mock} +} + +// Process provides a mock function with given fields: ctx, m, attrKVs +func (_m *ProtoProcessor) Process(ctx context.Context, m protoreflect.ProtoMessage, attrKVs ...interface{}) error { + var _ca []interface{} + _ca = append(_ca, ctx, m) + _ca = append(_ca, attrKVs...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Process") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, protoreflect.ProtoMessage, ...interface{}) error); ok { + r0 = rf(ctx, m, attrKVs...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ProtoProcessor_Process_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Process' +type ProtoProcessor_Process_Call struct { + *mock.Call +} + +// Process is a helper method to define mock.On call +// - ctx context.Context +// - m protoreflect.ProtoMessage +// - attrKVs ...interface{} +func (_e *ProtoProcessor_Expecter) Process(ctx interface{}, m interface{}, attrKVs ...interface{}) *ProtoProcessor_Process_Call { + return &ProtoProcessor_Process_Call{Call: _e.mock.On("Process", + append([]interface{}{ctx, m}, attrKVs...)...)} +} + +func (_c *ProtoProcessor_Process_Call) Run(run func(ctx context.Context, m protoreflect.ProtoMessage, attrKVs ...interface{})) *ProtoProcessor_Process_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(protoreflect.ProtoMessage), variadicArgs...) + }) + return _c +} + +func (_c *ProtoProcessor_Process_Call) Return(_a0 error) *ProtoProcessor_Process_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProtoProcessor_Process_Call) RunAndReturn(run func(context.Context, protoreflect.ProtoMessage, ...interface{}) error) *ProtoProcessor_Process_Call { + _c.Call.Return(run) + return _c +} + +// NewProtoProcessor creates a new instance of ProtoProcessor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewProtoProcessor(t interface { + mock.TestingT + Cleanup(func()) +}) *ProtoProcessor { + mock := &ProtoProcessor{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/capabilities/writetarget/config.go b/capabilities/writetarget/config.go new file mode 100644 index 0000000..1d31a95 --- /dev/null +++ b/capabilities/writetarget/config.go @@ -0,0 +1,17 @@ +package writetarget + +import ( + "time" +) + +// Config defines the write target component configuration. +type Config struct { + PollPeriod time.Duration + AcceptanceTimeout time.Duration +} + +// DefaultConfigSet is the default configuration for the write target component. +var DefaultConfigSet = Config{ + PollPeriod: 1 * time.Second, + AcceptanceTimeout: 10 * time.Second, +} diff --git a/capabilities/writetarget/messages.go b/capabilities/writetarget/messages.go new file mode 100644 index 0000000..5af170a --- /dev/null +++ b/capabilities/writetarget/messages.go @@ -0,0 +1,264 @@ +//nolint:gosec,errcheck // disable G115, errcheck +package writetarget + +import ( + "encoding/hex" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/types" + monitor "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/beholder" + + commonpb "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common" + wt "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform" +) + +// messageBuilder is a helper component to build monitoring messages +type messageBuilder struct { + ChainInfo monitor.ChainInfo + CapInfo capabilities.CapabilityInfo +} + +// NewMessageBuilder creates a new message builder +func NewMessageBuilder(chainInfo monitor.ChainInfo, capInfo capabilities.CapabilityInfo) *messageBuilder { + return &messageBuilder{ + ChainInfo: chainInfo, + CapInfo: capInfo, + } +} + +// reportInfo contains the report data for the request +type reportInfo struct { + reportContext []byte + report []byte + signersNum uint32 + + // Decoded report fields + reportID uint16 +} + +// requestInfo contains the request data for the capability triggered +type requestInfo struct { + tsStart int64 + + node string + forwarder string + receiver string + + request capabilities.CapabilityRequest + reportInfo *reportInfo + reportTransmissionState *TransmissionState +} + +func (m *messageBuilder) buildWriteError(i requestInfo, code uint32, summary, cause string) *wt.WriteError { + return &wt.WriteError{ + Code: code, + Summary: summary, + Cause: cause, + + Node: i.node, + Forwarder: i.forwarder, + Receiver: i.receiver, + ReportId: uint32(i.reportInfo.reportID), + + // Execution Context - Source + ExecutionContext: &commonpb.ExecutionContext{ + MetaSourceId: i.node, + + // Execution Context - Chain + MetaChainFamilyName: m.ChainInfo.FamilyName, + MetaChainId: m.ChainInfo.ChainID, + MetaNetworkName: m.ChainInfo.NetworkName, + MetaNetworkNameFull: m.ChainInfo.NetworkNameFull, + + // Execution Context - Workflow (capabilities.RequestMetadata) + MetaWorkflowId: i.request.Metadata.WorkflowID, + MetaWorkflowOwner: i.request.Metadata.WorkflowOwner, + MetaWorkflowExecutionId: i.request.Metadata.WorkflowExecutionID, + MetaWorkflowName: i.request.Metadata.WorkflowName, + MetaWorkflowDonId: i.request.Metadata.WorkflowDonID, + MetaWorkflowDonConfigVersion: i.request.Metadata.WorkflowDonConfigVersion, + MetaReferenceId: i.request.Metadata.ReferenceID, + + // Execution Context - Capability + MetaCapabilityType: string(m.CapInfo.CapabilityType), + MetaCapabilityId: m.CapInfo.ID, + MetaCapabilityTimestampStart: uint64(i.tsStart), + MetaCapabilityTimestampEmit: uint64(time.Now().UnixMilli()), + }, + } +} + +func (m *messageBuilder) buildWriteInitiated(i requestInfo) *wt.WriteInitiated { + return &wt.WriteInitiated{ + Node: i.node, + Forwarder: i.forwarder, + Receiver: i.receiver, + ReportId: uint32(i.reportInfo.reportID), + + ExecutionContext: &commonpb.ExecutionContext{ + // Execution Context - Source + MetaSourceId: i.node, + + // Execution Context - Chain + MetaChainFamilyName: m.ChainInfo.FamilyName, + MetaChainId: m.ChainInfo.ChainID, + MetaNetworkName: m.ChainInfo.NetworkName, + MetaNetworkNameFull: m.ChainInfo.NetworkNameFull, + + // Execution Context - Workflow (capabilities.RequestMetadata) + MetaWorkflowId: i.request.Metadata.WorkflowID, + MetaWorkflowOwner: i.request.Metadata.WorkflowOwner, + MetaWorkflowExecutionId: i.request.Metadata.WorkflowExecutionID, + MetaWorkflowName: i.request.Metadata.WorkflowName, + MetaWorkflowDonId: i.request.Metadata.WorkflowDonID, + MetaWorkflowDonConfigVersion: i.request.Metadata.WorkflowDonConfigVersion, + MetaReferenceId: i.request.Metadata.ReferenceID, + + // Execution Context - Capability + MetaCapabilityType: string(m.CapInfo.CapabilityType), + MetaCapabilityId: m.CapInfo.ID, + MetaCapabilityTimestampStart: uint64(i.tsStart), + MetaCapabilityTimestampEmit: uint64(time.Now().UnixMilli()), + }, + } +} + +func (m *messageBuilder) buildWriteSkipped(i requestInfo, reason string) *wt.WriteSkipped { + return &wt.WriteSkipped{ + Node: i.node, + Forwarder: i.forwarder, + Receiver: i.receiver, + ReportId: uint32(i.reportInfo.reportID), + Reason: reason, + + ExecutionContext: &commonpb.ExecutionContext{ + // Execution Context - Source + MetaSourceId: i.node, + + // Execution Context - Chain + MetaChainFamilyName: m.ChainInfo.FamilyName, + MetaChainId: m.ChainInfo.ChainID, + MetaNetworkName: m.ChainInfo.NetworkName, + MetaNetworkNameFull: m.ChainInfo.NetworkNameFull, + + // Execution Context - Workflow (capabilities.RequestMetadata) + MetaWorkflowId: i.request.Metadata.WorkflowID, + MetaWorkflowOwner: i.request.Metadata.WorkflowOwner, + MetaWorkflowExecutionId: i.request.Metadata.WorkflowExecutionID, + MetaWorkflowName: i.request.Metadata.WorkflowName, + MetaWorkflowDonId: i.request.Metadata.WorkflowDonID, + MetaWorkflowDonConfigVersion: i.request.Metadata.WorkflowDonConfigVersion, + MetaReferenceId: i.request.Metadata.ReferenceID, + + // Execution Context - Capability + MetaCapabilityType: string(m.CapInfo.CapabilityType), + MetaCapabilityId: m.CapInfo.ID, + MetaCapabilityTimestampStart: uint64(i.tsStart), + MetaCapabilityTimestampEmit: uint64(time.Now().UnixMilli()), + }, + } +} + +func (m *messageBuilder) buildWriteSent(i requestInfo, head types.Head, txID string) *wt.WriteSent { + return &wt.WriteSent{ + Node: i.node, + Forwarder: i.forwarder, + Receiver: i.receiver, + ReportId: uint32(i.reportInfo.reportID), + + TxId: txID, + + BlockData: &commonpb.BlockData{ + BlockHash: hex.EncodeToString(head.Hash), + BlockHeight: head.Height, + BlockTimestamp: head.Timestamp * 1000, // convert to milliseconds + }, + + ExecutionContext: &commonpb.ExecutionContext{ + + // Execution Context - Source + MetaSourceId: i.node, + + // Execution Context - Chain + MetaChainFamilyName: m.ChainInfo.FamilyName, + MetaChainId: m.ChainInfo.ChainID, + MetaNetworkName: m.ChainInfo.NetworkName, + MetaNetworkNameFull: m.ChainInfo.NetworkNameFull, + + // Execution Context - Workflow (capabilities.RequestMetadata) + MetaWorkflowId: i.request.Metadata.WorkflowID, + MetaWorkflowOwner: i.request.Metadata.WorkflowOwner, + MetaWorkflowExecutionId: i.request.Metadata.WorkflowExecutionID, + MetaWorkflowName: i.request.Metadata.WorkflowName, + MetaWorkflowDonId: i.request.Metadata.WorkflowDonID, + MetaWorkflowDonConfigVersion: i.request.Metadata.WorkflowDonConfigVersion, + MetaReferenceId: i.request.Metadata.ReferenceID, + + // Execution Context - Capability + MetaCapabilityType: string(m.CapInfo.CapabilityType), + MetaCapabilityId: m.CapInfo.ID, + MetaCapabilityTimestampStart: uint64(i.tsStart), + MetaCapabilityTimestampEmit: uint64(time.Now().UnixMilli()), + }, + } +} + +func (m *messageBuilder) buildWriteConfirmed(i requestInfo, head types.Head) *wt.WriteConfirmed { + rcfg := ReqConfig{} + processor := "" + reqConfig := i.request.Config + if reqConfig != nil { + // ignore errors and use default values + reqConfig.UnwrapTo(&rcfg) + processor = rcfg.Processor + } + + return &wt.WriteConfirmed{ + Node: i.node, + Forwarder: i.forwarder, + Receiver: i.receiver, + + ReportId: uint32(i.reportInfo.reportID), + ReportContext: i.reportInfo.reportContext, + Report: i.reportInfo.report, + SignersNum: i.reportInfo.signersNum, + + BlockData: &commonpb.BlockData{ + BlockHash: hex.EncodeToString(head.Hash), + BlockHeight: head.Height, + BlockTimestamp: head.Timestamp * 1000, // convert to milliseconds + }, + + // Transmission Info + Transmitter: i.reportTransmissionState.Transmitter, + Success: i.reportTransmissionState.Status == TransmissionStateSucceeded, + + ExecutionContext: &commonpb.ExecutionContext{ + // Execution Context - Source + MetaSourceId: i.node, + + // Execution Context - Chain + MetaChainFamilyName: m.ChainInfo.FamilyName, + MetaChainId: m.ChainInfo.ChainID, + MetaNetworkName: m.ChainInfo.NetworkName, + MetaNetworkNameFull: m.ChainInfo.NetworkNameFull, + + // Execution Context - Workflow (capabilities.RequestMetadata) + MetaWorkflowId: i.request.Metadata.WorkflowID, + MetaWorkflowOwner: i.request.Metadata.WorkflowOwner, + MetaWorkflowExecutionId: i.request.Metadata.WorkflowExecutionID, + MetaWorkflowName: i.request.Metadata.WorkflowName, + MetaWorkflowDonId: i.request.Metadata.WorkflowDonID, + MetaWorkflowDonConfigVersion: i.request.Metadata.WorkflowDonConfigVersion, + MetaReferenceId: i.request.Metadata.ReferenceID, + + // Execution Context - Capability + MetaCapabilityType: string(m.CapInfo.CapabilityType), + MetaCapabilityId: m.CapInfo.ID, + MetaCapabilityTimestampStart: uint64(i.tsStart), + MetaCapabilityTimestampEmit: uint64(time.Now().UnixMilli()), + }, + MetaCapabilityProcessor: processor, + } +} diff --git a/capabilities/writetarget/mocks/chain_service.go b/capabilities/writetarget/mocks/chain_service.go new file mode 100644 index 0000000..2edf6a8 --- /dev/null +++ b/capabilities/writetarget/mocks/chain_service.go @@ -0,0 +1,93 @@ +// Code generated by mockery v2.53.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + types "github.com/smartcontractkit/chainlink-common/pkg/types" + mock "github.com/stretchr/testify/mock" +) + +// ChainService is an autogenerated mock type for the chainService type +type ChainService struct { + mock.Mock +} + +type ChainService_Expecter struct { + mock *mock.Mock +} + +func (_m *ChainService) EXPECT() *ChainService_Expecter { + return &ChainService_Expecter{mock: &_m.Mock} +} + +// LatestHead provides a mock function with given fields: ctx +func (_m *ChainService) LatestHead(ctx context.Context) (types.Head, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for LatestHead") + } + + var r0 types.Head + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (types.Head, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) types.Head); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(types.Head) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ChainService_LatestHead_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LatestHead' +type ChainService_LatestHead_Call struct { + *mock.Call +} + +// LatestHead is a helper method to define mock.On call +// - ctx context.Context +func (_e *ChainService_Expecter) LatestHead(ctx interface{}) *ChainService_LatestHead_Call { + return &ChainService_LatestHead_Call{Call: _e.mock.On("LatestHead", ctx)} +} + +func (_c *ChainService_LatestHead_Call) Run(run func(ctx context.Context)) *ChainService_LatestHead_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *ChainService_LatestHead_Call) Return(_a0 types.Head, _a1 error) *ChainService_LatestHead_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ChainService_LatestHead_Call) RunAndReturn(run func(context.Context) (types.Head, error)) *ChainService_LatestHead_Call { + _c.Call.Return(run) + return _c +} + +// NewChainService creates a new instance of ChainService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewChainService(t interface { + mock.TestingT + Cleanup(func()) +}) *ChainService { + mock := &ChainService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/capabilities/writetarget/mocks/target_strategy.go b/capabilities/writetarget/mocks/target_strategy.go new file mode 100644 index 0000000..6f2740c --- /dev/null +++ b/capabilities/writetarget/mocks/target_strategy.go @@ -0,0 +1,219 @@ +// Code generated by mockery v2.53.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + capabilities "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + + mock "github.com/stretchr/testify/mock" + + types "github.com/smartcontractkit/chainlink-common/pkg/types" + + writetarget "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget" +) + +// TargetStrategy is an autogenerated mock type for the TargetStrategy type +type TargetStrategy struct { + mock.Mock +} + +type TargetStrategy_Expecter struct { + mock *mock.Mock +} + +func (_m *TargetStrategy) EXPECT() *TargetStrategy_Expecter { + return &TargetStrategy_Expecter{mock: &_m.Mock} +} + +// GetTransactionStatus provides a mock function with given fields: ctx, transactionID +func (_m *TargetStrategy) GetTransactionStatus(ctx context.Context, transactionID string) (types.TransactionStatus, error) { + ret := _m.Called(ctx, transactionID) + + if len(ret) == 0 { + panic("no return value specified for GetTransactionStatus") + } + + var r0 types.TransactionStatus + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (types.TransactionStatus, error)); ok { + return rf(ctx, transactionID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) types.TransactionStatus); ok { + r0 = rf(ctx, transactionID) + } else { + r0 = ret.Get(0).(types.TransactionStatus) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, transactionID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TargetStrategy_GetTransactionStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTransactionStatus' +type TargetStrategy_GetTransactionStatus_Call struct { + *mock.Call +} + +// GetTransactionStatus is a helper method to define mock.On call +// - ctx context.Context +// - transactionID string +func (_e *TargetStrategy_Expecter) GetTransactionStatus(ctx interface{}, transactionID interface{}) *TargetStrategy_GetTransactionStatus_Call { + return &TargetStrategy_GetTransactionStatus_Call{Call: _e.mock.On("GetTransactionStatus", ctx, transactionID)} +} + +func (_c *TargetStrategy_GetTransactionStatus_Call) Run(run func(ctx context.Context, transactionID string)) *TargetStrategy_GetTransactionStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *TargetStrategy_GetTransactionStatus_Call) Return(_a0 types.TransactionStatus, _a1 error) *TargetStrategy_GetTransactionStatus_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *TargetStrategy_GetTransactionStatus_Call) RunAndReturn(run func(context.Context, string) (types.TransactionStatus, error)) *TargetStrategy_GetTransactionStatus_Call { + _c.Call.Return(run) + return _c +} + +// QueryTransmissionState provides a mock function with given fields: ctx, reportID, request +func (_m *TargetStrategy) QueryTransmissionState(ctx context.Context, reportID uint16, request capabilities.CapabilityRequest) (*writetarget.TransmissionState, error) { + ret := _m.Called(ctx, reportID, request) + + if len(ret) == 0 { + panic("no return value specified for QueryTransmissionState") + } + + var r0 *writetarget.TransmissionState + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint16, capabilities.CapabilityRequest) (*writetarget.TransmissionState, error)); ok { + return rf(ctx, reportID, request) + } + if rf, ok := ret.Get(0).(func(context.Context, uint16, capabilities.CapabilityRequest) *writetarget.TransmissionState); ok { + r0 = rf(ctx, reportID, request) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*writetarget.TransmissionState) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint16, capabilities.CapabilityRequest) error); ok { + r1 = rf(ctx, reportID, request) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TargetStrategy_QueryTransmissionState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryTransmissionState' +type TargetStrategy_QueryTransmissionState_Call struct { + *mock.Call +} + +// QueryTransmissionState is a helper method to define mock.On call +// - ctx context.Context +// - reportID uint16 +// - request capabilities.CapabilityRequest +func (_e *TargetStrategy_Expecter) QueryTransmissionState(ctx interface{}, reportID interface{}, request interface{}) *TargetStrategy_QueryTransmissionState_Call { + return &TargetStrategy_QueryTransmissionState_Call{Call: _e.mock.On("QueryTransmissionState", ctx, reportID, request)} +} + +func (_c *TargetStrategy_QueryTransmissionState_Call) Run(run func(ctx context.Context, reportID uint16, request capabilities.CapabilityRequest)) *TargetStrategy_QueryTransmissionState_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint16), args[2].(capabilities.CapabilityRequest)) + }) + return _c +} + +func (_c *TargetStrategy_QueryTransmissionState_Call) Return(_a0 *writetarget.TransmissionState, _a1 error) *TargetStrategy_QueryTransmissionState_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *TargetStrategy_QueryTransmissionState_Call) RunAndReturn(run func(context.Context, uint16, capabilities.CapabilityRequest) (*writetarget.TransmissionState, error)) *TargetStrategy_QueryTransmissionState_Call { + _c.Call.Return(run) + return _c +} + +// TransmitReport provides a mock function with given fields: ctx, report, reportContext, signatures, request +func (_m *TargetStrategy) TransmitReport(ctx context.Context, report []byte, reportContext []byte, signatures [][]byte, request capabilities.CapabilityRequest) (string, error) { + ret := _m.Called(ctx, report, reportContext, signatures, request) + + if len(ret) == 0 { + panic("no return value specified for TransmitReport") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []byte, []byte, [][]byte, capabilities.CapabilityRequest) (string, error)); ok { + return rf(ctx, report, reportContext, signatures, request) + } + if rf, ok := ret.Get(0).(func(context.Context, []byte, []byte, [][]byte, capabilities.CapabilityRequest) string); ok { + r0 = rf(ctx, report, reportContext, signatures, request) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, []byte, []byte, [][]byte, capabilities.CapabilityRequest) error); ok { + r1 = rf(ctx, report, reportContext, signatures, request) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TargetStrategy_TransmitReport_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TransmitReport' +type TargetStrategy_TransmitReport_Call struct { + *mock.Call +} + +// TransmitReport is a helper method to define mock.On call +// - ctx context.Context +// - report []byte +// - reportContext []byte +// - signatures [][]byte +// - request capabilities.CapabilityRequest +func (_e *TargetStrategy_Expecter) TransmitReport(ctx interface{}, report interface{}, reportContext interface{}, signatures interface{}, request interface{}) *TargetStrategy_TransmitReport_Call { + return &TargetStrategy_TransmitReport_Call{Call: _e.mock.On("TransmitReport", ctx, report, reportContext, signatures, request)} +} + +func (_c *TargetStrategy_TransmitReport_Call) Run(run func(ctx context.Context, report []byte, reportContext []byte, signatures [][]byte, request capabilities.CapabilityRequest)) *TargetStrategy_TransmitReport_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]byte), args[2].([]byte), args[3].([][]byte), args[4].(capabilities.CapabilityRequest)) + }) + return _c +} + +func (_c *TargetStrategy_TransmitReport_Call) Return(_a0 string, _a1 error) *TargetStrategy_TransmitReport_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *TargetStrategy_TransmitReport_Call) RunAndReturn(run func(context.Context, []byte, []byte, [][]byte, capabilities.CapabilityRequest) (string, error)) *TargetStrategy_TransmitReport_Call { + _c.Call.Return(run) + return _c +} + +// NewTargetStrategy creates a new instance of TargetStrategy. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewTargetStrategy(t interface { + mock.TestingT + Cleanup(func()) +}) *TargetStrategy { + mock := &TargetStrategy{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/capabilities/writetarget/monitoring/pb/common/block_data.pb.go b/capabilities/writetarget/monitoring/pb/common/block_data.pb.go new file mode 100644 index 0000000..e5d9add --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/common/block_data.pb.go @@ -0,0 +1,143 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: block_data.proto + +package common + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// [Block Data] +type BlockData struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Head data - when was the event produced on-chain + BlockHash string `protobuf:"bytes,6,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` + BlockHeight string `protobuf:"bytes,7,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` + BlockTimestamp uint64 `protobuf:"varint,8,opt,name=block_timestamp,json=blockTimestamp,proto3" json:"block_timestamp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BlockData) Reset() { + *x = BlockData{} + mi := &file_block_data_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BlockData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockData) ProtoMessage() {} + +func (x *BlockData) ProtoReflect() protoreflect.Message { + mi := &file_block_data_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockData.ProtoReflect.Descriptor instead. +func (*BlockData) Descriptor() ([]byte, []int) { + return file_block_data_proto_rawDescGZIP(), []int{0} +} + +func (x *BlockData) GetBlockHash() string { + if x != nil { + return x.BlockHash + } + return "" +} + +func (x *BlockData) GetBlockHeight() string { + if x != nil { + return x.BlockHeight + } + return "" +} + +func (x *BlockData) GetBlockTimestamp() uint64 { + if x != nil { + return x.BlockTimestamp + } + return 0 +} + +var File_block_data_proto protoreflect.FileDescriptor + +const file_block_data_proto_rawDesc = "" + + "\n" + + "\x10block_data.proto\x12\x06common\"v\n" + + "\tBlockData\x12\x1d\n" + + "\n" + + "block_hash\x18\x06 \x01(\tR\tblockHash\x12!\n" + + "\fblock_height\x18\a \x01(\tR\vblockHeight\x12'\n" + + "\x0fblock_timestamp\x18\b \x01(\x04R\x0eblockTimestampBfZdgithub.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common;commonb\x06proto3" + +var ( + file_block_data_proto_rawDescOnce sync.Once + file_block_data_proto_rawDescData []byte +) + +func file_block_data_proto_rawDescGZIP() []byte { + file_block_data_proto_rawDescOnce.Do(func() { + file_block_data_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_block_data_proto_rawDesc), len(file_block_data_proto_rawDesc))) + }) + return file_block_data_proto_rawDescData +} + +var file_block_data_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_block_data_proto_goTypes = []any{ + (*BlockData)(nil), // 0: common.BlockData +} +var file_block_data_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_block_data_proto_init() } +func file_block_data_proto_init() { + if File_block_data_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_block_data_proto_rawDesc), len(file_block_data_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_block_data_proto_goTypes, + DependencyIndexes: file_block_data_proto_depIdxs, + MessageInfos: file_block_data_proto_msgTypes, + }.Build() + File_block_data_proto = out.File + file_block_data_proto_goTypes = nil + file_block_data_proto_depIdxs = nil +} diff --git a/capabilities/writetarget/monitoring/pb/common/block_data.proto b/capabilities/writetarget/monitoring/pb/common/block_data.proto new file mode 100644 index 0000000..84bcd5d --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/common/block_data.proto @@ -0,0 +1,13 @@ +syntax="proto3"; + +package common; +option go_package = "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common;common"; +// [Block Data] +message BlockData { + // Head data - when was the event produced on-chain + string block_hash = 6; + string block_height = 7; + uint64 block_timestamp = 8; +} + + diff --git a/capabilities/writetarget/monitoring/pb/common/execution_context.pb.go b/capabilities/writetarget/monitoring/pb/common/execution_context.pb.go new file mode 100644 index 0000000..8d7ef32 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/common/execution_context.pb.go @@ -0,0 +1,263 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: execution_context.proto + +package common + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// [Execution Context] +type ExecutionContext struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Execution Context - Source + MetaSourceId string `protobuf:"bytes,1,opt,name=meta_source_id,json=metaSourceId,proto3" json:"meta_source_id,omitempty"` + // Execution Context - Chain + MetaChainFamilyName string `protobuf:"bytes,2,opt,name=meta_chain_family_name,json=metaChainFamilyName,proto3" json:"meta_chain_family_name,omitempty"` + MetaChainId string `protobuf:"bytes,3,opt,name=meta_chain_id,json=metaChainId,proto3" json:"meta_chain_id,omitempty"` + MetaNetworkName string `protobuf:"bytes,4,opt,name=meta_network_name,json=metaNetworkName,proto3" json:"meta_network_name,omitempty"` + MetaNetworkNameFull string `protobuf:"bytes,5,opt,name=meta_network_name_full,json=metaNetworkNameFull,proto3" json:"meta_network_name_full,omitempty"` + // Execution Context - Workflow (capabilities.RequestMetadata) + MetaWorkflowId string `protobuf:"bytes,6,opt,name=meta_workflow_id,json=metaWorkflowId,proto3" json:"meta_workflow_id,omitempty"` + MetaWorkflowOwner string `protobuf:"bytes,7,opt,name=meta_workflow_owner,json=metaWorkflowOwner,proto3" json:"meta_workflow_owner,omitempty"` + MetaWorkflowExecutionId string `protobuf:"bytes,8,opt,name=meta_workflow_execution_id,json=metaWorkflowExecutionId,proto3" json:"meta_workflow_execution_id,omitempty"` + MetaWorkflowName string `protobuf:"bytes,9,opt,name=meta_workflow_name,json=metaWorkflowName,proto3" json:"meta_workflow_name,omitempty"` + MetaWorkflowDonId uint32 `protobuf:"varint,10,opt,name=meta_workflow_don_id,json=metaWorkflowDonId,proto3" json:"meta_workflow_don_id,omitempty"` + MetaWorkflowDonConfigVersion uint32 `protobuf:"varint,11,opt,name=meta_workflow_don_config_version,json=metaWorkflowDonConfigVersion,proto3" json:"meta_workflow_don_config_version,omitempty"` + MetaReferenceId string `protobuf:"bytes,12,opt,name=meta_reference_id,json=metaReferenceId,proto3" json:"meta_reference_id,omitempty"` + // Execution Context - Capability + MetaCapabilityType string `protobuf:"bytes,13,opt,name=meta_capability_type,json=metaCapabilityType,proto3" json:"meta_capability_type,omitempty"` + MetaCapabilityId string `protobuf:"bytes,14,opt,name=meta_capability_id,json=metaCapabilityId,proto3" json:"meta_capability_id,omitempty"` + MetaCapabilityTimestampStart uint64 `protobuf:"varint,15,opt,name=meta_capability_timestamp_start,json=metaCapabilityTimestampStart,proto3" json:"meta_capability_timestamp_start,omitempty"` + MetaCapabilityTimestampEmit uint64 `protobuf:"varint,16,opt,name=meta_capability_timestamp_emit,json=metaCapabilityTimestampEmit,proto3" json:"meta_capability_timestamp_emit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecutionContext) Reset() { + *x = ExecutionContext{} + mi := &file_execution_context_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecutionContext) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecutionContext) ProtoMessage() {} + +func (x *ExecutionContext) ProtoReflect() protoreflect.Message { + mi := &file_execution_context_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecutionContext.ProtoReflect.Descriptor instead. +func (*ExecutionContext) Descriptor() ([]byte, []int) { + return file_execution_context_proto_rawDescGZIP(), []int{0} +} + +func (x *ExecutionContext) GetMetaSourceId() string { + if x != nil { + return x.MetaSourceId + } + return "" +} + +func (x *ExecutionContext) GetMetaChainFamilyName() string { + if x != nil { + return x.MetaChainFamilyName + } + return "" +} + +func (x *ExecutionContext) GetMetaChainId() string { + if x != nil { + return x.MetaChainId + } + return "" +} + +func (x *ExecutionContext) GetMetaNetworkName() string { + if x != nil { + return x.MetaNetworkName + } + return "" +} + +func (x *ExecutionContext) GetMetaNetworkNameFull() string { + if x != nil { + return x.MetaNetworkNameFull + } + return "" +} + +func (x *ExecutionContext) GetMetaWorkflowId() string { + if x != nil { + return x.MetaWorkflowId + } + return "" +} + +func (x *ExecutionContext) GetMetaWorkflowOwner() string { + if x != nil { + return x.MetaWorkflowOwner + } + return "" +} + +func (x *ExecutionContext) GetMetaWorkflowExecutionId() string { + if x != nil { + return x.MetaWorkflowExecutionId + } + return "" +} + +func (x *ExecutionContext) GetMetaWorkflowName() string { + if x != nil { + return x.MetaWorkflowName + } + return "" +} + +func (x *ExecutionContext) GetMetaWorkflowDonId() uint32 { + if x != nil { + return x.MetaWorkflowDonId + } + return 0 +} + +func (x *ExecutionContext) GetMetaWorkflowDonConfigVersion() uint32 { + if x != nil { + return x.MetaWorkflowDonConfigVersion + } + return 0 +} + +func (x *ExecutionContext) GetMetaReferenceId() string { + if x != nil { + return x.MetaReferenceId + } + return "" +} + +func (x *ExecutionContext) GetMetaCapabilityType() string { + if x != nil { + return x.MetaCapabilityType + } + return "" +} + +func (x *ExecutionContext) GetMetaCapabilityId() string { + if x != nil { + return x.MetaCapabilityId + } + return "" +} + +func (x *ExecutionContext) GetMetaCapabilityTimestampStart() uint64 { + if x != nil { + return x.MetaCapabilityTimestampStart + } + return 0 +} + +func (x *ExecutionContext) GetMetaCapabilityTimestampEmit() uint64 { + if x != nil { + return x.MetaCapabilityTimestampEmit + } + return 0 +} + +var File_execution_context_proto protoreflect.FileDescriptor + +const file_execution_context_proto_rawDesc = "" + + "\n" + + "\x17execution_context.proto\x12\x06common\"\xc8\x06\n" + + "\x10ExecutionContext\x12$\n" + + "\x0emeta_source_id\x18\x01 \x01(\tR\fmetaSourceId\x123\n" + + "\x16meta_chain_family_name\x18\x02 \x01(\tR\x13metaChainFamilyName\x12\"\n" + + "\rmeta_chain_id\x18\x03 \x01(\tR\vmetaChainId\x12*\n" + + "\x11meta_network_name\x18\x04 \x01(\tR\x0fmetaNetworkName\x123\n" + + "\x16meta_network_name_full\x18\x05 \x01(\tR\x13metaNetworkNameFull\x12(\n" + + "\x10meta_workflow_id\x18\x06 \x01(\tR\x0emetaWorkflowId\x12.\n" + + "\x13meta_workflow_owner\x18\a \x01(\tR\x11metaWorkflowOwner\x12;\n" + + "\x1ameta_workflow_execution_id\x18\b \x01(\tR\x17metaWorkflowExecutionId\x12,\n" + + "\x12meta_workflow_name\x18\t \x01(\tR\x10metaWorkflowName\x12/\n" + + "\x14meta_workflow_don_id\x18\n" + + " \x01(\rR\x11metaWorkflowDonId\x12F\n" + + " meta_workflow_don_config_version\x18\v \x01(\rR\x1cmetaWorkflowDonConfigVersion\x12*\n" + + "\x11meta_reference_id\x18\f \x01(\tR\x0fmetaReferenceId\x120\n" + + "\x14meta_capability_type\x18\r \x01(\tR\x12metaCapabilityType\x12,\n" + + "\x12meta_capability_id\x18\x0e \x01(\tR\x10metaCapabilityId\x12E\n" + + "\x1fmeta_capability_timestamp_start\x18\x0f \x01(\x04R\x1cmetaCapabilityTimestampStart\x12C\n" + + "\x1emeta_capability_timestamp_emit\x18\x10 \x01(\x04R\x1bmetaCapabilityTimestampEmitBfZdgithub.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common;commonb\x06proto3" + +var ( + file_execution_context_proto_rawDescOnce sync.Once + file_execution_context_proto_rawDescData []byte +) + +func file_execution_context_proto_rawDescGZIP() []byte { + file_execution_context_proto_rawDescOnce.Do(func() { + file_execution_context_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_execution_context_proto_rawDesc), len(file_execution_context_proto_rawDesc))) + }) + return file_execution_context_proto_rawDescData +} + +var file_execution_context_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_execution_context_proto_goTypes = []any{ + (*ExecutionContext)(nil), // 0: common.ExecutionContext +} +var file_execution_context_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_execution_context_proto_init() } +func file_execution_context_proto_init() { + if File_execution_context_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_execution_context_proto_rawDesc), len(file_execution_context_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_execution_context_proto_goTypes, + DependencyIndexes: file_execution_context_proto_depIdxs, + MessageInfos: file_execution_context_proto_msgTypes, + }.Build() + File_execution_context_proto = out.File + file_execution_context_proto_goTypes = nil + file_execution_context_proto_depIdxs = nil +} diff --git a/capabilities/writetarget/monitoring/pb/common/execution_context.proto b/capabilities/writetarget/monitoring/pb/common/execution_context.proto new file mode 100644 index 0000000..4ee6369 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/common/execution_context.proto @@ -0,0 +1,30 @@ +syntax="proto3"; + +package common; +option go_package = "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common;common"; +// [Execution Context] +message ExecutionContext { + // Execution Context - Source + string meta_source_id = 1; + + // Execution Context - Chain + string meta_chain_family_name = 2; + string meta_chain_id = 3; + string meta_network_name = 4; + string meta_network_name_full = 5; + + // Execution Context - Workflow (capabilities.RequestMetadata) + string meta_workflow_id = 6; + string meta_workflow_owner = 7; + string meta_workflow_execution_id = 8; + string meta_workflow_name = 9; + uint32 meta_workflow_don_id = 10; + uint32 meta_workflow_don_config_version = 11; + string meta_reference_id = 12; + + // Execution Context - Capability + string meta_capability_type = 13; + string meta_capability_id = 14; + uint64 meta_capability_timestamp_start = 15; + uint64 meta_capability_timestamp_emit = 16; +} diff --git a/capabilities/writetarget/monitoring/pb/common/generate.go b/capabilities/writetarget/monitoring/pb/common/generate.go new file mode 100644 index 0000000..6f4105c --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/common/generate.go @@ -0,0 +1,6 @@ +// pb/common/generate.go +package common + +//go:generate protoc -I=. --go_out=paths=source_relative:. block_data.proto +//go:generate protoc -I=. --go_out=paths=source_relative:. execution_context.proto +//go:generate protoc -I=. --go_out=paths=source_relative:. transaction_data.proto diff --git a/capabilities/writetarget/monitoring/pb/common/transaction_data.pb.go b/capabilities/writetarget/monitoring/pb/common/transaction_data.pb.go new file mode 100644 index 0000000..bad3911 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/common/transaction_data.pb.go @@ -0,0 +1,162 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: transaction_data.proto + +package common + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// [Transaction Data] +type TransactionData struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Transaction data - info about the tx that mained the event (optional) + TxId string `protobuf:"bytes,10,opt,name=tx_id,json=txId,proto3" json:"tx_id,omitempty"` // TXM ref + TxHash string `protobuf:"bytes,11,opt,name=tx_hash,json=txHash,proto3" json:"tx_hash,omitempty"` + TxSender string `protobuf:"bytes,12,opt,name=tx_sender,json=txSender,proto3" json:"tx_sender,omitempty"` + TxReceiver string `protobuf:"bytes,13,opt,name=tx_receiver,json=txReceiver,proto3" json:"tx_receiver,omitempty"` + TxStatus string `protobuf:"bytes,14,opt,name=tx_status,json=txStatus,proto3" json:"tx_status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransactionData) Reset() { + *x = TransactionData{} + mi := &file_transaction_data_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransactionData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransactionData) ProtoMessage() {} + +func (x *TransactionData) ProtoReflect() protoreflect.Message { + mi := &file_transaction_data_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransactionData.ProtoReflect.Descriptor instead. +func (*TransactionData) Descriptor() ([]byte, []int) { + return file_transaction_data_proto_rawDescGZIP(), []int{0} +} + +func (x *TransactionData) GetTxId() string { + if x != nil { + return x.TxId + } + return "" +} + +func (x *TransactionData) GetTxHash() string { + if x != nil { + return x.TxHash + } + return "" +} + +func (x *TransactionData) GetTxSender() string { + if x != nil { + return x.TxSender + } + return "" +} + +func (x *TransactionData) GetTxReceiver() string { + if x != nil { + return x.TxReceiver + } + return "" +} + +func (x *TransactionData) GetTxStatus() string { + if x != nil { + return x.TxStatus + } + return "" +} + +var File_transaction_data_proto protoreflect.FileDescriptor + +const file_transaction_data_proto_rawDesc = "" + + "\n" + + "\x16transaction_data.proto\x12\x06common\"\x9a\x01\n" + + "\x0fTransactionData\x12\x13\n" + + "\x05tx_id\x18\n" + + " \x01(\tR\x04txId\x12\x17\n" + + "\atx_hash\x18\v \x01(\tR\x06txHash\x12\x1b\n" + + "\ttx_sender\x18\f \x01(\tR\btxSender\x12\x1f\n" + + "\vtx_receiver\x18\r \x01(\tR\n" + + "txReceiver\x12\x1b\n" + + "\ttx_status\x18\x0e \x01(\tR\btxStatusBfZdgithub.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common;commonb\x06proto3" + +var ( + file_transaction_data_proto_rawDescOnce sync.Once + file_transaction_data_proto_rawDescData []byte +) + +func file_transaction_data_proto_rawDescGZIP() []byte { + file_transaction_data_proto_rawDescOnce.Do(func() { + file_transaction_data_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transaction_data_proto_rawDesc), len(file_transaction_data_proto_rawDesc))) + }) + return file_transaction_data_proto_rawDescData +} + +var file_transaction_data_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_transaction_data_proto_goTypes = []any{ + (*TransactionData)(nil), // 0: common.TransactionData +} +var file_transaction_data_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_transaction_data_proto_init() } +func file_transaction_data_proto_init() { + if File_transaction_data_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_transaction_data_proto_rawDesc), len(file_transaction_data_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_transaction_data_proto_goTypes, + DependencyIndexes: file_transaction_data_proto_depIdxs, + MessageInfos: file_transaction_data_proto_msgTypes, + }.Build() + File_transaction_data_proto = out.File + file_transaction_data_proto_goTypes = nil + file_transaction_data_proto_depIdxs = nil +} diff --git a/capabilities/writetarget/monitoring/pb/common/transaction_data.proto b/capabilities/writetarget/monitoring/pb/common/transaction_data.proto new file mode 100644 index 0000000..b564a72 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/common/transaction_data.proto @@ -0,0 +1,15 @@ +syntax="proto3"; + +package common; +option go_package = "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common;common"; +// [Transaction Data] +message TransactionData { + // Transaction data - info about the tx that mained the event (optional) + string tx_id = 10; // TXM ref + string tx_hash = 11; + string tx_sender = 12; + string tx_receiver = 13; + string tx_status = 14; +} + + diff --git a/capabilities/writetarget/monitoring/pb/data-feeds/on-chain/registry/feed_updated.pb.go b/capabilities/writetarget/monitoring/pb/data-feeds/on-chain/registry/feed_updated.pb.go new file mode 100644 index 0000000..b2636c3 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/data-feeds/on-chain/registry/feed_updated.pb.go @@ -0,0 +1,213 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: feed_updated.proto + +package registry + +import ( + common "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// The on-chain FeedUpdated event which is extracted from the tx/event data or +// the write-target WriteConfirmed event, after a write was confirmed (@see message: write-target.WriteConfirmed). +type FeedUpdated struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Event data + FeedId string `protobuf:"bytes,1,opt,name=feed_id,json=feedId,proto3" json:"feed_id,omitempty"` // bytes as hex string for readability + ObservationsTimestamp uint32 `protobuf:"varint,2,opt,name=observations_timestamp,json=observationsTimestamp,proto3" json:"observations_timestamp,omitempty"` + Benchmark []byte `protobuf:"bytes,3,opt,name=benchmark,proto3" json:"benchmark,omitempty"` + Report []byte `protobuf:"bytes,4,opt,name=report,proto3" json:"report,omitempty"` + // Notice: benchmark_val is the benchmark i192 on-chain value decoded as an double (float64), scaled by number of decimals (e.g., 1e-18) + // This is the largest type Prometheus supports, and this conversion can overflow but so far was sufficient + // for most use-cases. For big numbers, benchmark bytes should be used instead. + // + // Set as `math.NaN()` if report data type not a number, or `+/-Inf` if number doesn't fit in double. + BenchmarkVal float64 `protobuf:"fixed64,5,opt,name=benchmark_val,json=benchmarkVal,proto3" json:"benchmark_val,omitempty"` + BlockData *common.BlockData `protobuf:"bytes,6,opt,name=block_data,json=blockData,proto3" json:"block_data,omitempty"` + Bundle []byte `protobuf:"bytes,9,opt,name=bundle,proto3" json:"bundle,omitempty"` + TransactionData *common.TransactionData `protobuf:"bytes,10,opt,name=transaction_data,json=transactionData,proto3" json:"transaction_data,omitempty"` + // [Execution Context] + ExecutionContext *common.ExecutionContext `protobuf:"bytes,20,opt,name=execution_context,json=executionContext,proto3" json:"execution_context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FeedUpdated) Reset() { + *x = FeedUpdated{} + mi := &file_feed_updated_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FeedUpdated) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeedUpdated) ProtoMessage() {} + +func (x *FeedUpdated) ProtoReflect() protoreflect.Message { + mi := &file_feed_updated_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeedUpdated.ProtoReflect.Descriptor instead. +func (*FeedUpdated) Descriptor() ([]byte, []int) { + return file_feed_updated_proto_rawDescGZIP(), []int{0} +} + +func (x *FeedUpdated) GetFeedId() string { + if x != nil { + return x.FeedId + } + return "" +} + +func (x *FeedUpdated) GetObservationsTimestamp() uint32 { + if x != nil { + return x.ObservationsTimestamp + } + return 0 +} + +func (x *FeedUpdated) GetBenchmark() []byte { + if x != nil { + return x.Benchmark + } + return nil +} + +func (x *FeedUpdated) GetReport() []byte { + if x != nil { + return x.Report + } + return nil +} + +func (x *FeedUpdated) GetBenchmarkVal() float64 { + if x != nil { + return x.BenchmarkVal + } + return 0 +} + +func (x *FeedUpdated) GetBlockData() *common.BlockData { + if x != nil { + return x.BlockData + } + return nil +} + +func (x *FeedUpdated) GetBundle() []byte { + if x != nil { + return x.Bundle + } + return nil +} + +func (x *FeedUpdated) GetTransactionData() *common.TransactionData { + if x != nil { + return x.TransactionData + } + return nil +} + +func (x *FeedUpdated) GetExecutionContext() *common.ExecutionContext { + if x != nil { + return x.ExecutionContext + } + return nil +} + +var File_feed_updated_proto protoreflect.FileDescriptor + +const file_feed_updated_proto_rawDesc = "" + + "\n" + + "\x12feed_updated.proto\x12\x1bdatafeeds.on_chain.registry\x1aEcapabilities/writetarget/monitoring/pb/common/execution_context.proto\x1a>capabilities/writetarget/monitoring/pb/common/block_data.proto\x1aDcapabilities/writetarget/monitoring/pb/common/transaction_data.proto\"\x8d\x03\n" + + "\vFeedUpdated\x12\x17\n" + + "\afeed_id\x18\x01 \x01(\tR\x06feedId\x125\n" + + "\x16observations_timestamp\x18\x02 \x01(\rR\x15observationsTimestamp\x12\x1c\n" + + "\tbenchmark\x18\x03 \x01(\fR\tbenchmark\x12\x16\n" + + "\x06report\x18\x04 \x01(\fR\x06report\x12#\n" + + "\rbenchmark_val\x18\x05 \x01(\x01R\fbenchmarkVal\x120\n" + + "\n" + + "block_data\x18\x06 \x01(\v2\x11.common.BlockDataR\tblockData\x12\x16\n" + + "\x06bundle\x18\t \x01(\fR\x06bundle\x12B\n" + + "\x10transaction_data\x18\n" + + " \x01(\v2\x17.common.TransactionDataR\x0ftransactionData\x12E\n" + + "\x11execution_context\x18\x14 \x01(\v2\x18.common.ExecutionContextR\x10executionContextB\fZ\n" + + ".;registryb\x06proto3" + +var ( + file_feed_updated_proto_rawDescOnce sync.Once + file_feed_updated_proto_rawDescData []byte +) + +func file_feed_updated_proto_rawDescGZIP() []byte { + file_feed_updated_proto_rawDescOnce.Do(func() { + file_feed_updated_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_feed_updated_proto_rawDesc), len(file_feed_updated_proto_rawDesc))) + }) + return file_feed_updated_proto_rawDescData +} + +var file_feed_updated_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_feed_updated_proto_goTypes = []any{ + (*FeedUpdated)(nil), // 0: datafeeds.on_chain.registry.FeedUpdated + (*common.BlockData)(nil), // 1: common.BlockData + (*common.TransactionData)(nil), // 2: common.TransactionData + (*common.ExecutionContext)(nil), // 3: common.ExecutionContext +} +var file_feed_updated_proto_depIdxs = []int32{ + 1, // 0: datafeeds.on_chain.registry.FeedUpdated.block_data:type_name -> common.BlockData + 2, // 1: datafeeds.on_chain.registry.FeedUpdated.transaction_data:type_name -> common.TransactionData + 3, // 2: datafeeds.on_chain.registry.FeedUpdated.execution_context:type_name -> common.ExecutionContext + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_feed_updated_proto_init() } +func file_feed_updated_proto_init() { + if File_feed_updated_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_feed_updated_proto_rawDesc), len(file_feed_updated_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feed_updated_proto_goTypes, + DependencyIndexes: file_feed_updated_proto_depIdxs, + MessageInfos: file_feed_updated_proto_msgTypes, + }.Build() + File_feed_updated_proto = out.File + file_feed_updated_proto_goTypes = nil + file_feed_updated_proto_depIdxs = nil +} diff --git a/capabilities/writetarget/monitoring/pb/data-feeds/on-chain/registry/feed_updated.proto b/capabilities/writetarget/monitoring/pb/data-feeds/on-chain/registry/feed_updated.proto new file mode 100644 index 0000000..800a80f --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/data-feeds/on-chain/registry/feed_updated.proto @@ -0,0 +1,34 @@ +syntax="proto3"; + +package datafeeds.on_chain.registry; +option go_package = ".;registry"; + +import "capabilities/writetarget/monitoring/pb/common/execution_context.proto"; +import "capabilities/writetarget/monitoring/pb/common/block_data.proto"; +import "capabilities/writetarget/monitoring/pb/common/transaction_data.proto"; + +// The on-chain FeedUpdated event which is extracted from the tx/event data or +// the write-target WriteConfirmed event, after a write was confirmed (@see message: write-target.WriteConfirmed). +message FeedUpdated { + // Event data + string feed_id = 1; // bytes as hex string for readability + uint32 observations_timestamp = 2; + bytes benchmark = 3; + bytes report = 4; + + // Notice: benchmark_val is the benchmark i192 on-chain value decoded as an double (float64), scaled by number of decimals (e.g., 1e-18) + // This is the largest type Prometheus supports, and this conversion can overflow but so far was sufficient + // for most use-cases. For big numbers, benchmark bytes should be used instead. + // + // Set as `math.NaN()` if report data type not a number, or `+/-Inf` if number doesn't fit in double. + double benchmark_val = 5; + + common.BlockData block_data = 6; + + bytes bundle = 9; + + common.TransactionData transaction_data = 10; + + // [Execution Context] + common.ExecutionContext execution_context = 20; +} diff --git a/capabilities/writetarget/monitoring/pb/data-feeds/on-chain/registry/generate.go b/capabilities/writetarget/monitoring/pb/data-feeds/on-chain/registry/generate.go new file mode 100644 index 0000000..d77c011 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/data-feeds/on-chain/registry/generate.go @@ -0,0 +1,3 @@ +package registry + +//go:generate protoc -I=../../../../../../.. -I=. --go_out=paths=source_relative:. feed_updated.proto diff --git a/capabilities/writetarget/monitoring/pb/data-feeds/on-chain/registry/metrics.go b/capabilities/writetarget/monitoring/pb/data-feeds/on-chain/registry/metrics.go new file mode 100644 index 0000000..3c13452 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/data-feeds/on-chain/registry/metrics.go @@ -0,0 +1,183 @@ +//nolint:gosec // disable G115 +package registry + +import ( + "context" + "fmt" + "strconv" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + + beholdercommon "github.com/smartcontractkit/chainlink-common/pkg/beholder" + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/beholder" +) + +// ns returns a namespaced metric name +func ns(name string) string { + return "data_feeds_on_chain_registry_" + name +} + +// Define a new struct for metrics +type Metrics struct { + // Define on FeedUpdated metrics + feedUpdated struct { + basic beholder.MetricsCapBasic + // specific to FeedUpdated + observationsTimestamp metric.Int64Gauge + duration metric.Int64Gauge // ts.emit - ts.observation + benchmark metric.Float64Gauge + blockTimestamp metric.Int64Gauge + blockNumber metric.Int64Gauge + } +} + +func NewMetrics() (*Metrics, error) { + // Define new metrics + m := &Metrics{} + + meter := beholdercommon.GetMeter() + + // Create new metrics + var err error + + // Define metrics configuration + feedUpdated := struct { + basic beholder.MetricsInfoCapBasic + // specific to FeedUpdated + observationsTimestamp beholder.MetricInfo + duration beholder.MetricInfo // ts.emit - ts.observation + benchmark beholder.MetricInfo + blockTimestamp beholder.MetricInfo + blockNumber beholder.MetricInfo + }{ + basic: beholder.NewMetricsInfoCapBasic(ns("feed_updated"), beholdercommon.ToSchemaFullName(&FeedUpdated{})), + observationsTimestamp: beholder.MetricInfo{ + Name: ns("feed_updated_observations_timestamp"), + Unit: "ms", + Description: "The observations timestamp for the latest confirmed update (as reported)", + }, + duration: beholder.MetricInfo{ + Name: ns("feed_updated_duration"), + Unit: "ms", + Description: "The duration (local) since observation to message: 'datafeeds.on-chain.registry.FeedUpdated' emit", + }, + benchmark: beholder.MetricInfo{ + Name: ns("feed_updated_benchmark"), + Unit: "", + Description: "The benchmark value for the latest confirmed update (as reported)", + }, + blockTimestamp: beholder.MetricInfo{ + Name: ns("feed_updated_block_timestamp"), + Unit: "ms", + Description: "The block timestamp at the latest confirmed update (as observed)", + }, + blockNumber: beholder.MetricInfo{ + Name: ns("feed_updated_block_number"), + Unit: "", + Description: "The block number at the latest confirmed update (as observed)", + }, + } + + m.feedUpdated.basic, err = beholder.NewMetricsCapBasic(feedUpdated.basic) + if err != nil { + return nil, fmt.Errorf("failed to create new basic metrics: %w", err) + } + + m.feedUpdated.observationsTimestamp, err = feedUpdated.observationsTimestamp.NewInt64Gauge(meter) + if err != nil { + return nil, fmt.Errorf("failed to create new gauge: %w", err) + } + + m.feedUpdated.duration, err = feedUpdated.duration.NewInt64Gauge(meter) + if err != nil { + return nil, fmt.Errorf("failed to create new gauge: %w", err) + } + + m.feedUpdated.benchmark, err = feedUpdated.benchmark.NewFloat64Gauge(meter) + if err != nil { + return nil, fmt.Errorf("failed to create new gauge: %w", err) + } + + m.feedUpdated.blockTimestamp, err = feedUpdated.blockTimestamp.NewInt64Gauge(meter) + if err != nil { + return nil, fmt.Errorf("failed to create new gauge: %w", err) + } + + m.feedUpdated.blockNumber, err = feedUpdated.blockNumber.NewInt64Gauge(meter) + if err != nil { + return nil, fmt.Errorf("failed to create new gauge: %w", err) + } + + return m, nil +} + +func (m *Metrics) OnFeedUpdated(ctx context.Context, msg *FeedUpdated, attrKVs ...any) error { + // Define attributes + attrs := metric.WithAttributes(msg.Attributes()...) + + // Emit basic metrics (count, timestamps) + start, emit := msg.ExecutionContext.MetaCapabilityTimestampStart, msg.ExecutionContext.MetaCapabilityTimestampEmit + m.feedUpdated.basic.RecordEmit(ctx, start, emit, msg.Attributes()...) + + // Timestamp e2e observation update + m.feedUpdated.observationsTimestamp.Record(ctx, int64(msg.ObservationsTimestamp), attrs) + observation := uint64(msg.ObservationsTimestamp) * 1000 // convert to milliseconds + m.feedUpdated.duration.Record(ctx, int64(emit-observation), attrs) + + // Benchmark + m.feedUpdated.benchmark.Record(ctx, msg.BenchmarkVal, attrs) + + // Block timestamp + m.feedUpdated.blockTimestamp.Record(ctx, int64(msg.BlockData.BlockTimestamp), attrs) + + // Block number + blockHeightVal, err := strconv.ParseInt(msg.BlockData.BlockHeight, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse block height: %w", err) + } + m.feedUpdated.blockNumber.Record(ctx, blockHeightVal, attrs) + + return nil +} + +// Attributes returns the attributes for the FeedUpdated message to be used in metrics +func (m *FeedUpdated) Attributes() []attribute.KeyValue { + context := beholder.ExecutionMetadata{ + // Execution Context - Source + SourceID: m.ExecutionContext.MetaSourceId, + // Execution Context - Chain + ChainFamilyName: m.ExecutionContext.MetaChainFamilyName, + ChainID: m.ExecutionContext.MetaChainId, + NetworkName: m.ExecutionContext.MetaNetworkName, + NetworkNameFull: m.ExecutionContext.MetaNetworkNameFull, + // Execution Context - Workflow (capabilities.RequestMetadata) + WorkflowID: m.ExecutionContext.MetaWorkflowId, + WorkflowOwner: m.ExecutionContext.MetaWorkflowOwner, + WorkflowExecutionID: m.ExecutionContext.MetaWorkflowExecutionId, + WorkflowName: m.ExecutionContext.MetaWorkflowName, + WorkflowDonID: m.ExecutionContext.MetaWorkflowDonId, + WorkflowDonConfigVersion: m.ExecutionContext.MetaWorkflowDonConfigVersion, + ReferenceID: m.ExecutionContext.MetaReferenceId, + // Execution Context - Capability + CapabilityType: m.ExecutionContext.MetaCapabilityType, + CapabilityID: m.ExecutionContext.MetaCapabilityId, + } + + attrs := []attribute.KeyValue{ + // Transaction Data + attribute.String("tx_sender", m.TransactionData.TxSender), + attribute.String("tx_receiver", m.TransactionData.TxReceiver), + + // Event Data + attribute.String("feed_id", m.FeedId), + // TODO: do we need these attributes? (available in WriteConfirmed) + // attribute.Int64("report_id", int64(m.ReportId)), // uint32 -> int64 + + // We mark confrmations by transmitter so we can query for only initial (fast) confirmations + // with PromQL, and ignore the slower confirmations by other signers for SLA measurements. + attribute.Bool("observed_by_transmitter", m.TransactionData.TxSender == m.ExecutionContext.MetaSourceId), // source_id == node account + } + + return append(attrs, context.Attributes()...) +} diff --git a/capabilities/writetarget/monitoring/pb/platform/error.go b/capabilities/writetarget/monitoring/pb/platform/error.go new file mode 100644 index 0000000..d43a876 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/error.go @@ -0,0 +1,13 @@ +package writetarget + +import ( + "fmt" + + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +// AsError returns the WriteError message as an (Go) error +func (e *WriteError) Error() string { + protoName := protoimpl.X.MessageTypeOf(e).Descriptor().FullName() + return fmt.Sprintf("%s [ERR-%v] - %s: %s", protoName, e.Code, e.Summary, e.Cause) +} diff --git a/capabilities/writetarget/monitoring/pb/platform/generate.go b/capabilities/writetarget/monitoring/pb/platform/generate.go new file mode 100644 index 0000000..0678143 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/generate.go @@ -0,0 +1,8 @@ +package writetarget + +//go:generate protoc -I=. -I=../../../../.. --go_out=paths=source_relative:. ./write_accepted.proto +//go:generate protoc -I=. -I=../../../../.. --go_out=paths=source_relative:. ./write_confirmed.proto +//go:generate protoc -I=. -I=../../../../.. --go_out=paths=source_relative:. ./write_error.proto +//go:generate protoc -I=. -I=../../../../.. --go_out=paths=source_relative:. ./write_initiated.proto +//go:generate protoc -I=. -I=../../../../.. --go_out=paths=source_relative:. ./write_sent.proto +//go:generate protoc -I=. -I=../../../../.. --go_out=paths=source_relative:. ./write_skipped.proto diff --git a/capabilities/writetarget/monitoring/pb/platform/metrics.go b/capabilities/writetarget/monitoring/pb/platform/metrics.go new file mode 100644 index 0000000..85ae733 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/metrics.go @@ -0,0 +1,358 @@ +//nolint:gosec // disable G115 +package writetarget + +import ( + "context" + "fmt" + "strconv" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + + beholdercommon "github.com/smartcontractkit/chainlink-common/pkg/beholder" + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/beholder" +) + +// ns returns a namespaced metric name +func ns(name string) string { + return fmt.Sprintf("platform_write_target_%s", name) +} + +// Define a new struct for metrics +type Metrics struct { + // Define on WriteInitiated metrics + writeInitiated struct { + basic beholder.MetricsCapBasic + } + // Define on WriteError metrics + writeError struct { + basic beholder.MetricsCapBasic + } + // Define on WriteSent metrics + writeSent struct { + basic beholder.MetricsCapBasic + // specific to WriteSent + blockTimestamp metric.Int64Gauge + blockNumber metric.Int64Gauge + } + // Define on WriteConfirmed metrics + writeConfirmed struct { + basic beholder.MetricsCapBasic + // specific to WriteConfirmed + blockTimestamp metric.Int64Gauge + blockNumber metric.Int64Gauge + signersNumber metric.Int64Gauge + } +} + +func NewMetrics() (*Metrics, error) { + // Define new metrics + m := &Metrics{} + + meter := beholdercommon.GetMeter() + + // Create new metrics + var err error + + // Define metrics configuration + writeInitiated := struct { + basic beholder.MetricsInfoCapBasic + }{ + basic: beholder.NewMetricsInfoCapBasic(ns("write_initiated"), beholdercommon.ToSchemaFullName(&WriteInitiated{})), + } + writeError := struct { + basic beholder.MetricsInfoCapBasic + }{ + basic: beholder.NewMetricsInfoCapBasic(ns("write_error"), beholdercommon.ToSchemaFullName(&WriteError{})), + } + writeSent := struct { + basic beholder.MetricsInfoCapBasic + // specific to WriteSent + blockTimestamp beholder.MetricInfo + blockNumber beholder.MetricInfo + }{ + basic: beholder.NewMetricsInfoCapBasic(ns("write_sent"), beholdercommon.ToSchemaFullName(&WriteSent{})), + blockTimestamp: beholder.MetricInfo{ + Name: ns("write_sent_block_timestamp"), + Unit: "ms", + Description: "The block timestamp at the latest sent write (as observed)", + }, + blockNumber: beholder.MetricInfo{ + Name: ns("write_sent_block_number"), + Unit: "", + Description: "The block number at the latest sent write (as observed)", + }, + } + writeConfirmed := struct { + basic beholder.MetricsInfoCapBasic + // specific to WriteSent + blockTimestamp beholder.MetricInfo + blockNumber beholder.MetricInfo + signersNumber beholder.MetricInfo + }{ + basic: beholder.NewMetricsInfoCapBasic(ns("write_confirmed"), beholdercommon.ToSchemaFullName(&WriteConfirmed{})), + blockTimestamp: beholder.MetricInfo{ + Name: ns("write_confirmed_block_timestamp"), + Unit: "ms", + Description: "The block timestamp for latest confirmed write (as observed)", + }, + blockNumber: beholder.MetricInfo{ + Name: ns("write_confirmed_block_number"), + Unit: "", + Description: "The block number for latest confirmed write (as observed)", + }, + signersNumber: beholder.MetricInfo{ + Name: ns("write_confirmed_signers_number"), + Unit: "", + Description: "The number of signers attached to the processed and confirmed write request", + }, + } + + // WriteInitiated + m.writeInitiated.basic, err = beholder.NewMetricsCapBasic(writeInitiated.basic) + if err != nil { + return nil, fmt.Errorf("failed to create new basic metrics: %w", err) + } + + // WriteError + m.writeError.basic, err = beholder.NewMetricsCapBasic(writeError.basic) + if err != nil { + return nil, fmt.Errorf("failed to create new basic metrics: %w", err) + } + + // WriteSent + m.writeSent.basic, err = beholder.NewMetricsCapBasic(writeSent.basic) + if err != nil { + return nil, fmt.Errorf("failed to create new basic metrics: %w", err) + } + + m.writeSent.blockTimestamp, err = writeSent.blockTimestamp.NewInt64Gauge(meter) + if err != nil { + return nil, fmt.Errorf("failed to create new gauge: %w", err) + } + + m.writeSent.blockNumber, err = writeSent.blockNumber.NewInt64Gauge(meter) + if err != nil { + return nil, fmt.Errorf("failed to create new gauge: %w", err) + } + + // WriteConfirmed + m.writeConfirmed.basic, err = beholder.NewMetricsCapBasic(writeConfirmed.basic) + if err != nil { + return nil, fmt.Errorf("failed to create new basic metrics: %w", err) + } + + m.writeConfirmed.blockTimestamp, err = writeConfirmed.blockTimestamp.NewInt64Gauge(meter) + if err != nil { + return nil, fmt.Errorf("failed to create new gauge: %w", err) + } + + m.writeConfirmed.blockNumber, err = writeConfirmed.blockNumber.NewInt64Gauge(meter) + if err != nil { + return nil, fmt.Errorf("failed to create new gauge: %w", err) + } + + m.writeConfirmed.signersNumber, err = writeConfirmed.signersNumber.NewInt64Gauge(meter) + if err != nil { + return nil, fmt.Errorf("failed to create new gauge: %w", err) + } + + return m, nil +} + +func (m *Metrics) OnWriteInitiated(ctx context.Context, msg *WriteInitiated, attrKVs ...any) error { + // Emit basic metrics (count, timestamps) + start, emit := msg.ExecutionContext.MetaCapabilityTimestampStart, msg.ExecutionContext.MetaCapabilityTimestampEmit + m.writeInitiated.basic.RecordEmit(ctx, start, emit, msg.Attributes()...) + return nil +} + +func (m *Metrics) OnWriteError(ctx context.Context, msg *WriteError, attrKVs ...any) error { + // Emit basic metrics (count, timestamps) + start, emit := msg.ExecutionContext.MetaCapabilityTimestampStart, msg.ExecutionContext.MetaCapabilityTimestampEmit + m.writeError.basic.RecordEmit(ctx, start, emit, msg.Attributes()...) + return nil +} + +func (m *Metrics) OnWriteSent(ctx context.Context, msg *WriteSent, attrKVs ...any) error { + // Define attributes + attrs := metric.WithAttributes(msg.Attributes()...) + + // Emit basic metrics (count, timestamps) + start, emit := msg.ExecutionContext.MetaCapabilityTimestampStart, msg.ExecutionContext.MetaCapabilityTimestampEmit + m.writeSent.basic.RecordEmit(ctx, start, emit, msg.Attributes()...) + + // Block timestamp + m.writeSent.blockTimestamp.Record(ctx, int64(msg.BlockData.BlockTimestamp), attrs) + + // Block number + blockHeightVal, err := strconv.ParseInt(msg.BlockData.BlockHeight, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse block height: %w", err) + } + m.writeSent.blockNumber.Record(ctx, blockHeightVal, attrs) + return nil +} + +func (m *Metrics) OnWriteConfirmed(ctx context.Context, msg *WriteConfirmed, attrKVs ...any) error { + // Define attributes + attrs := metric.WithAttributes(msg.Attributes()...) + + // Emit basic metrics (count, timestamps) + start, emit := msg.ExecutionContext.MetaCapabilityTimestampStart, msg.ExecutionContext.MetaCapabilityTimestampEmit + m.writeConfirmed.basic.RecordEmit(ctx, start, emit, msg.Attributes()...) + + // Signers number + m.writeConfirmed.signersNumber.Record(ctx, int64(msg.SignersNum), attrs) + + // Block timestamp + m.writeConfirmed.blockTimestamp.Record(ctx, int64(msg.BlockData.BlockTimestamp), attrs) + + // Block number + blockHeightVal, err := strconv.ParseInt(msg.BlockData.BlockHeight, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse block height: %w", err) + } + m.writeConfirmed.blockNumber.Record(ctx, blockHeightVal, attrs) + return nil +} + +// Attributes returns the attributes for the WriteInitiated message to be used in metrics +func (m *WriteInitiated) Attributes() []attribute.KeyValue { + context := beholder.ExecutionMetadata{ + // Execution Context - Source + SourceID: m.ExecutionContext.MetaSourceId, + // Execution Context - Chain + ChainFamilyName: m.ExecutionContext.MetaChainFamilyName, + ChainID: m.ExecutionContext.MetaChainId, + NetworkName: m.ExecutionContext.MetaNetworkName, + NetworkNameFull: m.ExecutionContext.MetaNetworkNameFull, + // Execution Context - Workflow (capabilities.RequestMetadata) + WorkflowID: m.ExecutionContext.MetaWorkflowId, + WorkflowOwner: m.ExecutionContext.MetaWorkflowOwner, + WorkflowExecutionID: m.ExecutionContext.MetaWorkflowExecutionId, + WorkflowName: m.ExecutionContext.MetaWorkflowName, + WorkflowDonID: m.ExecutionContext.MetaWorkflowDonId, + WorkflowDonConfigVersion: m.ExecutionContext.MetaWorkflowDonConfigVersion, + ReferenceID: m.ExecutionContext.MetaReferenceId, + // Execution Context - Capability + CapabilityType: m.ExecutionContext.MetaCapabilityType, + CapabilityID: m.ExecutionContext.MetaCapabilityId, + } + + attrs := []attribute.KeyValue{ + attribute.String("node", m.Node), + attribute.String("forwarder", m.Forwarder), + attribute.String("receiver", m.Receiver), + attribute.Int64("report_id", int64(m.ReportId)), // uint32 -> int64 + } + + return append(attrs, context.Attributes()...) +} + +// Attributes returns the attributes for the WriteError message to be used in metrics +func (m *WriteError) Attributes() []attribute.KeyValue { + context := beholder.ExecutionMetadata{ + // Execution Context - Source + SourceID: m.ExecutionContext.MetaSourceId, + // Execution Context - Chain + ChainFamilyName: m.ExecutionContext.MetaChainFamilyName, + ChainID: m.ExecutionContext.MetaChainId, + NetworkName: m.ExecutionContext.MetaNetworkName, + NetworkNameFull: m.ExecutionContext.MetaNetworkNameFull, + // Execution Context - Workflow (capabilities.RequestMetadata) + WorkflowID: m.ExecutionContext.MetaWorkflowId, + WorkflowOwner: m.ExecutionContext.MetaWorkflowOwner, + WorkflowExecutionID: m.ExecutionContext.MetaWorkflowExecutionId, + WorkflowName: m.ExecutionContext.MetaWorkflowName, + WorkflowDonID: m.ExecutionContext.MetaWorkflowDonId, + WorkflowDonConfigVersion: m.ExecutionContext.MetaWorkflowDonConfigVersion, + ReferenceID: m.ExecutionContext.MetaReferenceId, + // Execution Context - Capability + CapabilityType: m.ExecutionContext.MetaCapabilityType, + CapabilityID: m.ExecutionContext.MetaCapabilityId, + } + + attrs := []attribute.KeyValue{ + attribute.String("node", m.Node), + attribute.String("forwarder", m.Forwarder), + attribute.String("receiver", m.Receiver), + attribute.Int64("report_id", int64(m.ReportId)), // uint32 -> int64 + // Error information + attribute.Int64("code", int64(m.Code)), // uint32 -> int64 + attribute.String("summary", m.Summary), + } + + return append(attrs, context.Attributes()...) +} + +// Attributes returns the attributes for the WriteSent message to be used in metrics +func (m *WriteSent) Attributes() []attribute.KeyValue { + context := beholder.ExecutionMetadata{ + // Execution Context - Source + SourceID: m.ExecutionContext.MetaSourceId, + // Execution Context - Chain + ChainFamilyName: m.ExecutionContext.MetaChainFamilyName, + ChainID: m.ExecutionContext.MetaChainId, + NetworkName: m.ExecutionContext.MetaNetworkName, + NetworkNameFull: m.ExecutionContext.MetaNetworkNameFull, + // Execution Context - Workflow (capabilities.RequestMetadata) + WorkflowID: m.ExecutionContext.MetaWorkflowId, + WorkflowOwner: m.ExecutionContext.MetaWorkflowOwner, + WorkflowExecutionID: m.ExecutionContext.MetaWorkflowExecutionId, + WorkflowName: m.ExecutionContext.MetaWorkflowName, + WorkflowDonID: m.ExecutionContext.MetaWorkflowDonId, + WorkflowDonConfigVersion: m.ExecutionContext.MetaWorkflowDonConfigVersion, + ReferenceID: m.ExecutionContext.MetaReferenceId, + // Execution Context - Capability + CapabilityType: m.ExecutionContext.MetaCapabilityType, + CapabilityID: m.ExecutionContext.MetaCapabilityId, + } + + attrs := []attribute.KeyValue{ + attribute.String("node", m.Node), + attribute.String("forwarder", m.Forwarder), + attribute.String("receiver", m.Receiver), + attribute.Int64("report_id", int64(m.ReportId)), // uint32 -> int64 + } + + return append(attrs, context.Attributes()...) +} + +// Attributes returns the attributes for the WriteConfirmed message to be used in metrics +func (m *WriteConfirmed) Attributes() []attribute.KeyValue { + context := beholder.ExecutionMetadata{ + // Execution Context - Source + SourceID: m.ExecutionContext.MetaSourceId, + // Execution Context - Chain + ChainFamilyName: m.ExecutionContext.MetaChainFamilyName, + ChainID: m.ExecutionContext.MetaChainId, + NetworkName: m.ExecutionContext.MetaNetworkName, + NetworkNameFull: m.ExecutionContext.MetaNetworkNameFull, + // Execution Context - Workflow (capabilities.RequestMetadata) + WorkflowID: m.ExecutionContext.MetaWorkflowId, + WorkflowOwner: m.ExecutionContext.MetaWorkflowOwner, + WorkflowExecutionID: m.ExecutionContext.MetaWorkflowExecutionId, + WorkflowName: m.ExecutionContext.MetaWorkflowName, + WorkflowDonID: m.ExecutionContext.MetaWorkflowDonId, + WorkflowDonConfigVersion: m.ExecutionContext.MetaWorkflowDonConfigVersion, + ReferenceID: m.ExecutionContext.MetaReferenceId, + // Execution Context - Capability + CapabilityType: m.ExecutionContext.MetaCapabilityType, + CapabilityID: m.ExecutionContext.MetaCapabilityId, + } + + attrs := []attribute.KeyValue{ + attribute.String("node", m.Node), + attribute.String("forwarder", m.Forwarder), + attribute.String("receiver", m.Receiver), + attribute.Int64("report_id", int64(m.ReportId)), // uint32 -> int64 + attribute.String("transmitter", m.Transmitter), + attribute.Bool("success", m.Success), + // We mark confrmations by transmitter so we can query for only initial (fast) confirmations + // with PromQL, and ignore the slower confirmations by other signers for SLA measurements. + attribute.Bool("observed_by_transmitter", m.Transmitter == m.Node), + } + + return append(attrs, context.Attributes()...) +} diff --git a/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/decode.go b/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/decode.go new file mode 100644 index 0000000..c076825 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/decode.go @@ -0,0 +1,40 @@ +package forwarder + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/report/platform" + + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common" + wt_msg "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform" +) + +// DecodeAsReportProcessed decodes a 'platform.write-target.WriteConfirmed' message +// as a 'keytone.forwarder.ReportProcessed' message +func DecodeAsReportProcessed(m *wt_msg.WriteConfirmed) (*ReportProcessed, error) { + // Decode the confirmed report (WT -> platform forwarder contract event akka. Keystone) + r, err := platform.Decode(m.Report) + if err != nil { + return nil, fmt.Errorf("failed to decode report: %w", err) + } + + return &ReportProcessed{ + // Event data + Receiver: m.Receiver, + WorkflowExecutionId: r.ExecutionID, + ReportId: m.ReportId, + Success: m.Success, + + BlockData: m.BlockData, + + // Transaction data - info about the tx that mained the event (optional) + // Notice: we skip SOME head/tx data here (unknown), as we map from 'platform.write-target.WriteConfirmed' + // and not from tx/event data (e.g., 'platform.write-target.WriteTxConfirmed') + TransactionData: &common.TransactionData{ + TxSender: m.Transmitter, + TxReceiver: m.Forwarder, + }, + + ExecutionContext: m.ExecutionContext, + }, nil +} diff --git a/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/decode_test.go b/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/decode_test.go new file mode 100644 index 0000000..92d3daa --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/decode_test.go @@ -0,0 +1,109 @@ +//nolint:govet // disable govet +package forwarder + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common" + wt_msg "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform" +) + +func TestDecodeAsReportProcessed(t *testing.T) { + encoded, err := NewTestReport(t, []byte{}) + require.NoError(t, err) + + // Define test cases + tests := []struct { + name string + input wt_msg.WriteConfirmed + expected ReportProcessed + wantErr bool + }{ + { + name: "Valid input", + input: wt_msg.WriteConfirmed{ + Node: "example-node", + Forwarder: "example-forwarder", + Receiver: "example-receiver", + + // Report Info + ReportId: 123, + ReportContext: []byte{}, + Report: encoded, // Example valid byte slice + SignersNum: 2, + + // Transmission Info + Transmitter: "example-transmitter", + Success: true, + + BlockData: &common.BlockData{ + // Block Info + BlockHash: "0xaa", + BlockHeight: "17", + BlockTimestamp: 0x66f5bf69, + }, + }, + expected: ReportProcessed{ + Receiver: "example-receiver", + WorkflowExecutionId: "0102030405060708090a0b0c0d0e0f1000000000000000000000000000000000", + ReportId: 123, + Success: true, + + BlockData: &common.BlockData{ + // Block Info + BlockHash: "0xaa", + BlockHeight: "17", + BlockTimestamp: 0x66f5bf69, + }, + + TransactionData: &common.TransactionData{ + TxSender: "example-transmitter", + TxReceiver: "example-forwarder", + }, + }, + wantErr: false, + }, + { + name: "Invalid input", + input: wt_msg.WriteConfirmed{ + Node: "example-node", + Forwarder: "example-forwarder", + Receiver: "example-receiver", + + // Report Info + ReportId: 123, + ReportContext: []byte{}, + Report: []byte{0x01, 0x02, 0x03, 0x04}, // Example invalid byte slice + SignersNum: 2, + + // Transmission Info + Transmitter: "example-transmitter", + Success: true, + + BlockData: &common.BlockData{ + // Block Info + BlockHash: "0xaa", + BlockHeight: "17", + BlockTimestamp: 0x66f5bf69, + }, + }, + expected: ReportProcessed{}, + wantErr: true, + }, + // Add more test cases as needed + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := DecodeAsReportProcessed(&tt.input) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expected, *result) + } + }) + } +} diff --git a/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/generate.go b/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/generate.go new file mode 100644 index 0000000..2d49449 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/generate.go @@ -0,0 +1,3 @@ +package forwarder + +//go:generate protoc -I=../../../../../../.. -I=. --go_out=paths=source_relative:. report_processed.proto diff --git a/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/metrics.go b/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/metrics.go new file mode 100644 index 0000000..5deab9e --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/metrics.go @@ -0,0 +1,140 @@ +//nolint:gosec // disable G115 +package forwarder + +import ( + "context" + "fmt" + "strconv" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + + beholdercommon "github.com/smartcontractkit/chainlink-common/pkg/beholder" + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/beholder" +) + +// ns returns a namespaced metric name +func ns(name string) string { + return fmt.Sprintf("platform_on_chain_forwarder_%s", name) +} + +// Define a new struct for metrics +type Metrics struct { + // Define on ReportProcessed metrics + reportProcessed struct { + basic beholder.MetricsCapBasic + // specific to ReportProcessed + blockTimestamp metric.Int64Gauge + blockNumber metric.Int64Gauge + } +} + +func NewMetrics() (*Metrics, error) { + // Define new metrics + m := &Metrics{} + + meter := beholdercommon.GetMeter() + + // Create new metrics + var err error + + reportProcessed := struct { + basic beholder.MetricsInfoCapBasic + // specific to ReportProcessed + blockTimestamp beholder.MetricInfo + blockNumber beholder.MetricInfo + }{ + basic: beholder.NewMetricsInfoCapBasic(ns("report_processed"), beholdercommon.ToSchemaFullName(&ReportProcessed{})), + blockTimestamp: beholder.MetricInfo{ + Name: ns("report_processed_block_timestamp"), + Unit: "ms", + Description: "The block timestamp at the latest confirmed write (as observed)", + }, + blockNumber: beholder.MetricInfo{ + Name: ns("report_processed_block_number"), + Unit: "", + Description: "The block number at the latest confirmed write (as observed)", + }, + } + + fmt.Println("report processed name:", beholdercommon.ToSchemaFullName(&ReportProcessed{})) + + m.reportProcessed.basic, err = beholder.NewMetricsCapBasic(reportProcessed.basic) + if err != nil { + return nil, fmt.Errorf("failed to create new basic metrics: %w", err) + } + + m.reportProcessed.blockTimestamp, err = reportProcessed.blockTimestamp.NewInt64Gauge(meter) + if err != nil { + return nil, fmt.Errorf("failed to create new gauge: %w", err) + } + + m.reportProcessed.blockNumber, err = reportProcessed.blockNumber.NewInt64Gauge(meter) + if err != nil { + return nil, fmt.Errorf("failed to create new gauge: %w", err) + } + + return m, nil +} + +func (m *Metrics) OnReportProcessed(ctx context.Context, msg *ReportProcessed, attrKVs ...any) error { + // Define attributes + attrs := metric.WithAttributes(msg.Attributes()...) + + // Emit basic metrics (count, timestamps) + start, emit := msg.ExecutionContext.MetaCapabilityTimestampStart, msg.ExecutionContext.MetaCapabilityTimestampEmit + m.reportProcessed.basic.RecordEmit(ctx, start, emit, msg.Attributes()...) + + // Block timestamp + m.reportProcessed.blockTimestamp.Record(ctx, int64(msg.BlockData.BlockTimestamp), attrs) + + // Block number + blockHeightVal, err := strconv.ParseInt(msg.BlockData.BlockHeight, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse block height: %w", err) + } + m.reportProcessed.blockNumber.Record(ctx, blockHeightVal, attrs) + + return nil +} + +// Attributes returns the attributes for the ReportProcessed message to be used in metrics +func (m *ReportProcessed) Attributes() []attribute.KeyValue { + context := beholder.ExecutionMetadata{ + // Execution Context - Source + SourceID: m.ExecutionContext.MetaSourceId, + // Execution Context - Chain + ChainFamilyName: m.ExecutionContext.MetaChainFamilyName, + ChainID: m.ExecutionContext.MetaChainId, + NetworkName: m.ExecutionContext.MetaNetworkName, + NetworkNameFull: m.ExecutionContext.MetaNetworkNameFull, + // Execution Context - Workflow (capabilities.RequestMetadata) + WorkflowID: m.ExecutionContext.MetaWorkflowId, + WorkflowOwner: m.ExecutionContext.MetaWorkflowOwner, + WorkflowExecutionID: m.ExecutionContext.MetaWorkflowExecutionId, + WorkflowName: m.ExecutionContext.MetaWorkflowName, + WorkflowDonID: m.ExecutionContext.MetaWorkflowDonId, + WorkflowDonConfigVersion: m.ExecutionContext.MetaWorkflowDonConfigVersion, + ReferenceID: m.ExecutionContext.MetaReferenceId, + // Execution Context - Capability + CapabilityType: m.ExecutionContext.MetaCapabilityType, + CapabilityID: m.ExecutionContext.MetaCapabilityId, + } + + attrs := []attribute.KeyValue{ + // Transaction Data + attribute.String("tx_sender", m.TransactionData.TxSender), + attribute.String("tx_receiver", m.TransactionData.TxReceiver), + + // Event Data + attribute.String("receiver", m.Receiver), + attribute.Int64("report_id", int64(m.ReportId)), // uint32 -> int64 + attribute.Bool("success", m.Success), + + // We mark confrmations by transmitter so we can query for only initial (fast) confirmations + // with PromQL, and ignore the slower confirmations by other signers for SLA measurements. + attribute.Bool("observed_by_transmitter", m.TransactionData.TxSender == m.ExecutionContext.MetaSourceId), // source_id == node account + } + + return append(attrs, context.Attributes()...) +} diff --git a/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/report_processed.pb.go b/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/report_processed.pb.go new file mode 100644 index 0000000..7766ec2 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/report_processed.pb.go @@ -0,0 +1,188 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: report_processed.proto + +package forwarder + +import ( + common "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// The on-chain ReportProcessed event which is extracted from the tx/event data or +// the write-target WriteConfirmed event, after a write was confirmed (@see message: platform.write-target.WriteConfirmed). +type ReportProcessed struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Event data + Receiver string `protobuf:"bytes,1,opt,name=receiver,proto3" json:"receiver,omitempty"` + WorkflowExecutionId string `protobuf:"bytes,2,opt,name=workflow_execution_id,json=workflowExecutionId,proto3" json:"workflow_execution_id,omitempty"` // bytes as hex string for readability + ReportId uint32 `protobuf:"varint,3,opt,name=report_id,json=reportId,proto3" json:"report_id,omitempty"` + Success bool `protobuf:"varint,4,opt,name=success,proto3" json:"success,omitempty"` + BlockData *common.BlockData `protobuf:"bytes,6,opt,name=block_data,json=blockData,proto3" json:"block_data,omitempty"` + TransactionData *common.TransactionData `protobuf:"bytes,10,opt,name=transaction_data,json=transactionData,proto3" json:"transaction_data,omitempty"` + ExecutionContext *common.ExecutionContext `protobuf:"bytes,20,opt,name=execution_context,json=executionContext,proto3" json:"execution_context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReportProcessed) Reset() { + *x = ReportProcessed{} + mi := &file_report_processed_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReportProcessed) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReportProcessed) ProtoMessage() {} + +func (x *ReportProcessed) ProtoReflect() protoreflect.Message { + mi := &file_report_processed_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReportProcessed.ProtoReflect.Descriptor instead. +func (*ReportProcessed) Descriptor() ([]byte, []int) { + return file_report_processed_proto_rawDescGZIP(), []int{0} +} + +func (x *ReportProcessed) GetReceiver() string { + if x != nil { + return x.Receiver + } + return "" +} + +func (x *ReportProcessed) GetWorkflowExecutionId() string { + if x != nil { + return x.WorkflowExecutionId + } + return "" +} + +func (x *ReportProcessed) GetReportId() uint32 { + if x != nil { + return x.ReportId + } + return 0 +} + +func (x *ReportProcessed) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *ReportProcessed) GetBlockData() *common.BlockData { + if x != nil { + return x.BlockData + } + return nil +} + +func (x *ReportProcessed) GetTransactionData() *common.TransactionData { + if x != nil { + return x.TransactionData + } + return nil +} + +func (x *ReportProcessed) GetExecutionContext() *common.ExecutionContext { + if x != nil { + return x.ExecutionContext + } + return nil +} + +var File_report_processed_proto protoreflect.FileDescriptor + +const file_report_processed_proto_rawDesc = "" + + "\n" + + "\x16report_processed.proto\x12\x1bplatform.on_chain.forwarder\x1aEcapabilities/writetarget/monitoring/pb/common/execution_context.proto\x1a>capabilities/writetarget/monitoring/pb/common/block_data.proto\x1aDcapabilities/writetarget/monitoring/pb/common/transaction_data.proto\"\xd5\x02\n" + + "\x0fReportProcessed\x12\x1a\n" + + "\breceiver\x18\x01 \x01(\tR\breceiver\x122\n" + + "\x15workflow_execution_id\x18\x02 \x01(\tR\x13workflowExecutionId\x12\x1b\n" + + "\treport_id\x18\x03 \x01(\rR\breportId\x12\x18\n" + + "\asuccess\x18\x04 \x01(\bR\asuccess\x120\n" + + "\n" + + "block_data\x18\x06 \x01(\v2\x11.common.BlockDataR\tblockData\x12B\n" + + "\x10transaction_data\x18\n" + + " \x01(\v2\x17.common.TransactionDataR\x0ftransactionData\x12E\n" + + "\x11execution_context\x18\x14 \x01(\v2\x18.common.ExecutionContextR\x10executionContextB~Z|github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder;forwarderb\x06proto3" + +var ( + file_report_processed_proto_rawDescOnce sync.Once + file_report_processed_proto_rawDescData []byte +) + +func file_report_processed_proto_rawDescGZIP() []byte { + file_report_processed_proto_rawDescOnce.Do(func() { + file_report_processed_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_report_processed_proto_rawDesc), len(file_report_processed_proto_rawDesc))) + }) + return file_report_processed_proto_rawDescData +} + +var file_report_processed_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_report_processed_proto_goTypes = []any{ + (*ReportProcessed)(nil), // 0: platform.on_chain.forwarder.ReportProcessed + (*common.BlockData)(nil), // 1: common.BlockData + (*common.TransactionData)(nil), // 2: common.TransactionData + (*common.ExecutionContext)(nil), // 3: common.ExecutionContext +} +var file_report_processed_proto_depIdxs = []int32{ + 1, // 0: platform.on_chain.forwarder.ReportProcessed.block_data:type_name -> common.BlockData + 2, // 1: platform.on_chain.forwarder.ReportProcessed.transaction_data:type_name -> common.TransactionData + 3, // 2: platform.on_chain.forwarder.ReportProcessed.execution_context:type_name -> common.ExecutionContext + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_report_processed_proto_init() } +func file_report_processed_proto_init() { + if File_report_processed_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_report_processed_proto_rawDesc), len(file_report_processed_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_report_processed_proto_goTypes, + DependencyIndexes: file_report_processed_proto_depIdxs, + MessageInfos: file_report_processed_proto_msgTypes, + }.Build() + File_report_processed_proto = out.File + file_report_processed_proto_goTypes = nil + file_report_processed_proto_depIdxs = nil +} diff --git a/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/report_processed.proto b/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/report_processed.proto new file mode 100644 index 0000000..f3d8ae1 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/report_processed.proto @@ -0,0 +1,24 @@ +syntax="proto3"; + +package platform.on_chain.forwarder; + +import "capabilities/writetarget/monitoring/pb/common/execution_context.proto"; +import "capabilities/writetarget/monitoring/pb/common/block_data.proto"; +import "capabilities/writetarget/monitoring/pb/common/transaction_data.proto"; + +option go_package = "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder;forwarder"; + + +// The on-chain ReportProcessed event which is extracted from the tx/event data or +// the write-target WriteConfirmed event, after a write was confirmed (@see message: platform.write-target.WriteConfirmed). +message ReportProcessed { + // Event data + string receiver = 1; + string workflow_execution_id = 2; // bytes as hex string for readability + uint32 report_id = 3; + bool success = 4; + + common.BlockData block_data = 6; + common.TransactionData transaction_data = 10; + common.ExecutionContext execution_context = 20; +} diff --git a/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/utils.go b/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/utils.go new file mode 100644 index 0000000..8d3dcd8 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder/utils.go @@ -0,0 +1,31 @@ +package forwarder + +import ( + "testing" + + ocr3types "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3/types" + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/report/platform" +) + +func NewTestReport(t *testing.T, data []byte) ([]byte, error) { + metadata := ocr3types.Metadata{ + Version: 1, + ExecutionID: "0102030405060708090a0b0c0d0e0f1000000000000000000000000000000000", + Timestamp: 1620000000, + DONID: 1, + DONConfigVersion: 1, + WorkflowID: "1234567890123456789012345678901234567890123456789012345678901234", + WorkflowName: "12", + WorkflowOwner: "1234567890123456789012345678901234567890", + ReportID: "1234", + } + report := platform.Report{ + Metadata: metadata, + Data: data, + } + encoded, err := report.Encode() + if err != nil { + return nil, err + } + return encoded, nil +} diff --git a/capabilities/writetarget/monitoring/pb/platform/write_accepted.pb.go b/capabilities/writetarget/monitoring/pb/platform/write_accepted.pb.go new file mode 100644 index 0000000..ec3c2fd --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/write_accepted.pb.go @@ -0,0 +1,189 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: write_accepted.proto + +package writetarget + +import ( + common "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// WT sent a transaction and it was accepted on-chain +// +// Notice: we publish txId (TXM ref) and txHash (in the future should be available here) +type WriteAccepted struct { + state protoimpl.MessageState `protogen:"open.v1"` + Node string `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + Forwarder string `protobuf:"bytes,2,opt,name=forwarder,proto3" json:"forwarder,omitempty"` + Receiver string `protobuf:"bytes,3,opt,name=receiver,proto3" json:"receiver,omitempty"` + // Report Info + ReportId uint32 `protobuf:"varint,4,opt,name=report_id,json=reportId,proto3" json:"report_id,omitempty"` + BlockData *common.BlockData `protobuf:"bytes,6,opt,name=block_data,json=blockData,proto3" json:"block_data,omitempty"` + TransactionData *common.TransactionData `protobuf:"bytes,10,opt,name=transaction_data,json=transactionData,proto3" json:"transaction_data,omitempty"` + ExecutionContext *common.ExecutionContext `protobuf:"bytes,20,opt,name=execution_context,json=executionContext,proto3" json:"execution_context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WriteAccepted) Reset() { + *x = WriteAccepted{} + mi := &file_write_accepted_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WriteAccepted) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteAccepted) ProtoMessage() {} + +func (x *WriteAccepted) ProtoReflect() protoreflect.Message { + mi := &file_write_accepted_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteAccepted.ProtoReflect.Descriptor instead. +func (*WriteAccepted) Descriptor() ([]byte, []int) { + return file_write_accepted_proto_rawDescGZIP(), []int{0} +} + +func (x *WriteAccepted) GetNode() string { + if x != nil { + return x.Node + } + return "" +} + +func (x *WriteAccepted) GetForwarder() string { + if x != nil { + return x.Forwarder + } + return "" +} + +func (x *WriteAccepted) GetReceiver() string { + if x != nil { + return x.Receiver + } + return "" +} + +func (x *WriteAccepted) GetReportId() uint32 { + if x != nil { + return x.ReportId + } + return 0 +} + +func (x *WriteAccepted) GetBlockData() *common.BlockData { + if x != nil { + return x.BlockData + } + return nil +} + +func (x *WriteAccepted) GetTransactionData() *common.TransactionData { + if x != nil { + return x.TransactionData + } + return nil +} + +func (x *WriteAccepted) GetExecutionContext() *common.ExecutionContext { + if x != nil { + return x.ExecutionContext + } + return nil +} + +var File_write_accepted_proto protoreflect.FileDescriptor + +const file_write_accepted_proto_rawDesc = "" + + "\n" + + "\x14write_accepted.proto\x12\x15platform.write_target\x1aEcapabilities/writetarget/monitoring/pb/common/execution_context.proto\x1a>capabilities/writetarget/monitoring/pb/common/block_data.proto\x1aDcapabilities/writetarget/monitoring/pb/common/transaction_data.proto\"\xb7\x02\n" + + "\rWriteAccepted\x12\x12\n" + + "\x04node\x18\x01 \x01(\tR\x04node\x12\x1c\n" + + "\tforwarder\x18\x02 \x01(\tR\tforwarder\x12\x1a\n" + + "\breceiver\x18\x03 \x01(\tR\breceiver\x12\x1b\n" + + "\treport_id\x18\x04 \x01(\rR\breportId\x120\n" + + "\n" + + "block_data\x18\x06 \x01(\v2\x11.common.BlockDataR\tblockData\x12B\n" + + "\x10transaction_data\x18\n" + + " \x01(\v2\x17.common.TransactionDataR\x0ftransactionData\x12E\n" + + "\x11execution_context\x18\x14 \x01(\v2\x18.common.ExecutionContextR\x10executionContextBmZkgithub.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform;writetargetb\x06proto3" + +var ( + file_write_accepted_proto_rawDescOnce sync.Once + file_write_accepted_proto_rawDescData []byte +) + +func file_write_accepted_proto_rawDescGZIP() []byte { + file_write_accepted_proto_rawDescOnce.Do(func() { + file_write_accepted_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_write_accepted_proto_rawDesc), len(file_write_accepted_proto_rawDesc))) + }) + return file_write_accepted_proto_rawDescData +} + +var file_write_accepted_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_write_accepted_proto_goTypes = []any{ + (*WriteAccepted)(nil), // 0: platform.write_target.WriteAccepted + (*common.BlockData)(nil), // 1: common.BlockData + (*common.TransactionData)(nil), // 2: common.TransactionData + (*common.ExecutionContext)(nil), // 3: common.ExecutionContext +} +var file_write_accepted_proto_depIdxs = []int32{ + 1, // 0: platform.write_target.WriteAccepted.block_data:type_name -> common.BlockData + 2, // 1: platform.write_target.WriteAccepted.transaction_data:type_name -> common.TransactionData + 3, // 2: platform.write_target.WriteAccepted.execution_context:type_name -> common.ExecutionContext + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_write_accepted_proto_init() } +func file_write_accepted_proto_init() { + if File_write_accepted_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_write_accepted_proto_rawDesc), len(file_write_accepted_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_write_accepted_proto_goTypes, + DependencyIndexes: file_write_accepted_proto_depIdxs, + MessageInfos: file_write_accepted_proto_msgTypes, + }.Build() + File_write_accepted_proto = out.File + file_write_accepted_proto_goTypes = nil + file_write_accepted_proto_depIdxs = nil +} diff --git a/capabilities/writetarget/monitoring/pb/platform/write_accepted.proto b/capabilities/writetarget/monitoring/pb/platform/write_accepted.proto new file mode 100644 index 0000000..ebdd3ef --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/write_accepted.proto @@ -0,0 +1,24 @@ +syntax="proto3"; + +package platform.write_target; +option go_package = "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform;writetarget"; + +import "capabilities/writetarget/monitoring/pb/common/execution_context.proto"; +import "capabilities/writetarget/monitoring/pb/common/block_data.proto"; +import "capabilities/writetarget/monitoring/pb/common/transaction_data.proto"; + +// WT sent a transaction and it was accepted on-chain +// +// Notice: we publish txId (TXM ref) and txHash (in the future should be available here) +message WriteAccepted { + string node = 1; + string forwarder = 2; + string receiver = 3; + + // Report Info + uint32 report_id = 4; + + common.BlockData block_data = 6; + common.TransactionData transaction_data = 10; + common.ExecutionContext execution_context = 20; +} diff --git a/capabilities/writetarget/monitoring/pb/platform/write_confirmed.pb.go b/capabilities/writetarget/monitoring/pb/platform/write_confirmed.pb.go new file mode 100644 index 0000000..72539a7 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/write_confirmed.pb.go @@ -0,0 +1,234 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: write_confirmed.proto + +package writetarget + +import ( + common "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// WT confirmed the report was successfully written on-chain +type WriteConfirmed struct { + state protoimpl.MessageState `protogen:"open.v1"` + Node string `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + Forwarder string `protobuf:"bytes,2,opt,name=forwarder,proto3" json:"forwarder,omitempty"` + Receiver string `protobuf:"bytes,3,opt,name=receiver,proto3" json:"receiver,omitempty"` + // Report Info + ReportId uint32 `protobuf:"varint,4,opt,name=report_id,json=reportId,proto3" json:"report_id,omitempty"` + ReportContext []byte `protobuf:"bytes,5,opt,name=report_context,json=reportContext,proto3" json:"report_context,omitempty"` + Report []byte `protobuf:"bytes,6,opt,name=report,proto3" json:"report,omitempty"` + SignersNum uint32 `protobuf:"varint,7,opt,name=signers_num,json=signersNum,proto3" json:"signers_num,omitempty"` + // When was the report confirmed on-chain + BlockData *common.BlockData `protobuf:"bytes,9,opt,name=block_data,json=blockData,proto3" json:"block_data,omitempty"` + // Transmission Info + Transmitter string `protobuf:"bytes,12,opt,name=transmitter,proto3" json:"transmitter,omitempty"` + Success bool `protobuf:"varint,13,opt,name=success,proto3" json:"success,omitempty"` // TODO: what about EVM's TransmissionInfo parity? + // [Execution Context] + ExecutionContext *common.ExecutionContext `protobuf:"bytes,20,opt,name=execution_context,json=executionContext,proto3" json:"execution_context,omitempty"` + // encoder processor info + MetaCapabilityProcessor string `protobuf:"bytes,21,opt,name=meta_capability_processor,json=metaCapabilityProcessor,proto3" json:"meta_capability_processor,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WriteConfirmed) Reset() { + *x = WriteConfirmed{} + mi := &file_write_confirmed_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WriteConfirmed) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteConfirmed) ProtoMessage() {} + +func (x *WriteConfirmed) ProtoReflect() protoreflect.Message { + mi := &file_write_confirmed_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteConfirmed.ProtoReflect.Descriptor instead. +func (*WriteConfirmed) Descriptor() ([]byte, []int) { + return file_write_confirmed_proto_rawDescGZIP(), []int{0} +} + +func (x *WriteConfirmed) GetNode() string { + if x != nil { + return x.Node + } + return "" +} + +func (x *WriteConfirmed) GetForwarder() string { + if x != nil { + return x.Forwarder + } + return "" +} + +func (x *WriteConfirmed) GetReceiver() string { + if x != nil { + return x.Receiver + } + return "" +} + +func (x *WriteConfirmed) GetReportId() uint32 { + if x != nil { + return x.ReportId + } + return 0 +} + +func (x *WriteConfirmed) GetReportContext() []byte { + if x != nil { + return x.ReportContext + } + return nil +} + +func (x *WriteConfirmed) GetReport() []byte { + if x != nil { + return x.Report + } + return nil +} + +func (x *WriteConfirmed) GetSignersNum() uint32 { + if x != nil { + return x.SignersNum + } + return 0 +} + +func (x *WriteConfirmed) GetBlockData() *common.BlockData { + if x != nil { + return x.BlockData + } + return nil +} + +func (x *WriteConfirmed) GetTransmitter() string { + if x != nil { + return x.Transmitter + } + return "" +} + +func (x *WriteConfirmed) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *WriteConfirmed) GetExecutionContext() *common.ExecutionContext { + if x != nil { + return x.ExecutionContext + } + return nil +} + +func (x *WriteConfirmed) GetMetaCapabilityProcessor() string { + if x != nil { + return x.MetaCapabilityProcessor + } + return "" +} + +var File_write_confirmed_proto protoreflect.FileDescriptor + +const file_write_confirmed_proto_rawDesc = "" + + "\n" + + "\x15write_confirmed.proto\x12\x15platform.write_target\x1aEcapabilities/writetarget/monitoring/pb/common/execution_context.proto\x1a>capabilities/writetarget/monitoring/pb/common/block_data.proto\"\xcc\x03\n" + + "\x0eWriteConfirmed\x12\x12\n" + + "\x04node\x18\x01 \x01(\tR\x04node\x12\x1c\n" + + "\tforwarder\x18\x02 \x01(\tR\tforwarder\x12\x1a\n" + + "\breceiver\x18\x03 \x01(\tR\breceiver\x12\x1b\n" + + "\treport_id\x18\x04 \x01(\rR\breportId\x12%\n" + + "\x0ereport_context\x18\x05 \x01(\fR\rreportContext\x12\x16\n" + + "\x06report\x18\x06 \x01(\fR\x06report\x12\x1f\n" + + "\vsigners_num\x18\a \x01(\rR\n" + + "signersNum\x120\n" + + "\n" + + "block_data\x18\t \x01(\v2\x11.common.BlockDataR\tblockData\x12 \n" + + "\vtransmitter\x18\f \x01(\tR\vtransmitter\x12\x18\n" + + "\asuccess\x18\r \x01(\bR\asuccess\x12E\n" + + "\x11execution_context\x18\x14 \x01(\v2\x18.common.ExecutionContextR\x10executionContext\x12:\n" + + "\x19meta_capability_processor\x18\x15 \x01(\tR\x17metaCapabilityProcessorBmZkgithub.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform;writetargetb\x06proto3" + +var ( + file_write_confirmed_proto_rawDescOnce sync.Once + file_write_confirmed_proto_rawDescData []byte +) + +func file_write_confirmed_proto_rawDescGZIP() []byte { + file_write_confirmed_proto_rawDescOnce.Do(func() { + file_write_confirmed_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_write_confirmed_proto_rawDesc), len(file_write_confirmed_proto_rawDesc))) + }) + return file_write_confirmed_proto_rawDescData +} + +var file_write_confirmed_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_write_confirmed_proto_goTypes = []any{ + (*WriteConfirmed)(nil), // 0: platform.write_target.WriteConfirmed + (*common.BlockData)(nil), // 1: common.BlockData + (*common.ExecutionContext)(nil), // 2: common.ExecutionContext +} +var file_write_confirmed_proto_depIdxs = []int32{ + 1, // 0: platform.write_target.WriteConfirmed.block_data:type_name -> common.BlockData + 2, // 1: platform.write_target.WriteConfirmed.execution_context:type_name -> common.ExecutionContext + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_write_confirmed_proto_init() } +func file_write_confirmed_proto_init() { + if File_write_confirmed_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_write_confirmed_proto_rawDesc), len(file_write_confirmed_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_write_confirmed_proto_goTypes, + DependencyIndexes: file_write_confirmed_proto_depIdxs, + MessageInfos: file_write_confirmed_proto_msgTypes, + }.Build() + File_write_confirmed_proto = out.File + file_write_confirmed_proto_goTypes = nil + file_write_confirmed_proto_depIdxs = nil +} diff --git a/capabilities/writetarget/monitoring/pb/platform/write_confirmed.proto b/capabilities/writetarget/monitoring/pb/platform/write_confirmed.proto new file mode 100644 index 0000000..7f1b5f3 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/write_confirmed.proto @@ -0,0 +1,36 @@ +syntax="proto3"; + +package platform.write_target; +option go_package = "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform;writetarget"; + +import "capabilities/writetarget/monitoring/pb/common/execution_context.proto"; +import "capabilities/writetarget/monitoring/pb/common/block_data.proto"; + +// WT confirmed the report was successfully written on-chain +message WriteConfirmed { + string node = 1; + string forwarder = 2; + string receiver = 3; + + // Report Info + uint32 report_id = 4; + bytes report_context = 5; + bytes report = 6; + uint32 signers_num = 7; + + // TODO: Is the report confirmed finalized on-chain, or just observed? + + // When was the report confirmed on-chain + common.BlockData block_data = 9; + + // Transmission Info + string transmitter = 12; + bool success = 13; + // TODO: what about EVM's TransmissionInfo parity? + + // [Execution Context] + common.ExecutionContext execution_context = 20; + + // encoder processor info + string meta_capability_processor = 21; +} diff --git a/capabilities/writetarget/monitoring/pb/platform/write_error.pb.go b/capabilities/writetarget/monitoring/pb/platform/write_error.pb.go new file mode 100644 index 0000000..c68608e --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/write_error.pb.go @@ -0,0 +1,192 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: write_error.proto + +package writetarget + +import ( + common "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// WT errored while processing write request +type WriteError struct { + state protoimpl.MessageState `protogen:"open.v1"` + Code uint32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` + Summary string `protobuf:"bytes,2,opt,name=summary,proto3" json:"summary,omitempty"` + Cause string `protobuf:"bytes,3,opt,name=cause,proto3" json:"cause,omitempty"` + Node string `protobuf:"bytes,4,opt,name=node,proto3" json:"node,omitempty"` + Forwarder string `protobuf:"bytes,5,opt,name=forwarder,proto3" json:"forwarder,omitempty"` + Receiver string `protobuf:"bytes,6,opt,name=receiver,proto3" json:"receiver,omitempty"` + // Report Info + ReportId uint32 `protobuf:"varint,7,opt,name=report_id,json=reportId,proto3" json:"report_id,omitempty"` + // [Execution Context] + ExecutionContext *common.ExecutionContext `protobuf:"bytes,20,opt,name=execution_context,json=executionContext,proto3" json:"execution_context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WriteError) Reset() { + *x = WriteError{} + mi := &file_write_error_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WriteError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteError) ProtoMessage() {} + +func (x *WriteError) ProtoReflect() protoreflect.Message { + mi := &file_write_error_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteError.ProtoReflect.Descriptor instead. +func (*WriteError) Descriptor() ([]byte, []int) { + return file_write_error_proto_rawDescGZIP(), []int{0} +} + +func (x *WriteError) GetCode() uint32 { + if x != nil { + return x.Code + } + return 0 +} + +func (x *WriteError) GetSummary() string { + if x != nil { + return x.Summary + } + return "" +} + +func (x *WriteError) GetCause() string { + if x != nil { + return x.Cause + } + return "" +} + +func (x *WriteError) GetNode() string { + if x != nil { + return x.Node + } + return "" +} + +func (x *WriteError) GetForwarder() string { + if x != nil { + return x.Forwarder + } + return "" +} + +func (x *WriteError) GetReceiver() string { + if x != nil { + return x.Receiver + } + return "" +} + +func (x *WriteError) GetReportId() uint32 { + if x != nil { + return x.ReportId + } + return 0 +} + +func (x *WriteError) GetExecutionContext() *common.ExecutionContext { + if x != nil { + return x.ExecutionContext + } + return nil +} + +var File_write_error_proto protoreflect.FileDescriptor + +const file_write_error_proto_rawDesc = "" + + "\n" + + "\x11write_error.proto\x12\x15platform.write_target\x1aEcapabilities/writetarget/monitoring/pb/common/execution_context.proto\"\x82\x02\n" + + "\n" + + "WriteError\x12\x12\n" + + "\x04code\x18\x01 \x01(\rR\x04code\x12\x18\n" + + "\asummary\x18\x02 \x01(\tR\asummary\x12\x14\n" + + "\x05cause\x18\x03 \x01(\tR\x05cause\x12\x12\n" + + "\x04node\x18\x04 \x01(\tR\x04node\x12\x1c\n" + + "\tforwarder\x18\x05 \x01(\tR\tforwarder\x12\x1a\n" + + "\breceiver\x18\x06 \x01(\tR\breceiver\x12\x1b\n" + + "\treport_id\x18\a \x01(\rR\breportId\x12E\n" + + "\x11execution_context\x18\x14 \x01(\v2\x18.common.ExecutionContextR\x10executionContextBmZkgithub.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform;writetargetb\x06proto3" + +var ( + file_write_error_proto_rawDescOnce sync.Once + file_write_error_proto_rawDescData []byte +) + +func file_write_error_proto_rawDescGZIP() []byte { + file_write_error_proto_rawDescOnce.Do(func() { + file_write_error_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_write_error_proto_rawDesc), len(file_write_error_proto_rawDesc))) + }) + return file_write_error_proto_rawDescData +} + +var file_write_error_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_write_error_proto_goTypes = []any{ + (*WriteError)(nil), // 0: platform.write_target.WriteError + (*common.ExecutionContext)(nil), // 1: common.ExecutionContext +} +var file_write_error_proto_depIdxs = []int32{ + 1, // 0: platform.write_target.WriteError.execution_context:type_name -> common.ExecutionContext + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_write_error_proto_init() } +func file_write_error_proto_init() { + if File_write_error_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_write_error_proto_rawDesc), len(file_write_error_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_write_error_proto_goTypes, + DependencyIndexes: file_write_error_proto_depIdxs, + MessageInfos: file_write_error_proto_msgTypes, + }.Build() + File_write_error_proto = out.File + file_write_error_proto_goTypes = nil + file_write_error_proto_depIdxs = nil +} diff --git a/capabilities/writetarget/monitoring/pb/platform/write_error.proto b/capabilities/writetarget/monitoring/pb/platform/write_error.proto new file mode 100644 index 0000000..51c8453 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/write_error.proto @@ -0,0 +1,23 @@ +syntax="proto3"; + +package platform.write_target; +option go_package = "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform;writetarget"; + +import "capabilities/writetarget/monitoring/pb/common/execution_context.proto"; + +// WT errored while processing write request +message WriteError { + uint32 code = 1; + string summary = 2; + string cause = 3; + + string node = 4; + string forwarder = 5; + string receiver = 6; + + // Report Info + uint32 report_id = 7; + + // [Execution Context] + common.ExecutionContext execution_context = 20; +} diff --git a/capabilities/writetarget/monitoring/pb/platform/write_initiated.pb.go b/capabilities/writetarget/monitoring/pb/platform/write_initiated.pb.go new file mode 100644 index 0000000..c1b10b0 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/write_initiated.pb.go @@ -0,0 +1,164 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: write_initiated.proto + +package writetarget + +import ( + common "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// WT initiated the processing of the write request +type WriteInitiated struct { + state protoimpl.MessageState `protogen:"open.v1"` + Node string `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + Forwarder string `protobuf:"bytes,2,opt,name=forwarder,proto3" json:"forwarder,omitempty"` + Receiver string `protobuf:"bytes,3,opt,name=receiver,proto3" json:"receiver,omitempty"` + // Report Info + ReportId uint32 `protobuf:"varint,4,opt,name=report_id,json=reportId,proto3" json:"report_id,omitempty"` + // [Execution Context] + ExecutionContext *common.ExecutionContext `protobuf:"bytes,20,opt,name=execution_context,json=executionContext,proto3" json:"execution_context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WriteInitiated) Reset() { + *x = WriteInitiated{} + mi := &file_write_initiated_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WriteInitiated) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteInitiated) ProtoMessage() {} + +func (x *WriteInitiated) ProtoReflect() protoreflect.Message { + mi := &file_write_initiated_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteInitiated.ProtoReflect.Descriptor instead. +func (*WriteInitiated) Descriptor() ([]byte, []int) { + return file_write_initiated_proto_rawDescGZIP(), []int{0} +} + +func (x *WriteInitiated) GetNode() string { + if x != nil { + return x.Node + } + return "" +} + +func (x *WriteInitiated) GetForwarder() string { + if x != nil { + return x.Forwarder + } + return "" +} + +func (x *WriteInitiated) GetReceiver() string { + if x != nil { + return x.Receiver + } + return "" +} + +func (x *WriteInitiated) GetReportId() uint32 { + if x != nil { + return x.ReportId + } + return 0 +} + +func (x *WriteInitiated) GetExecutionContext() *common.ExecutionContext { + if x != nil { + return x.ExecutionContext + } + return nil +} + +var File_write_initiated_proto protoreflect.FileDescriptor + +const file_write_initiated_proto_rawDesc = "" + + "\n" + + "\x15write_initiated.proto\x12\x15platform.write_target\x1aEcapabilities/writetarget/monitoring/pb/common/execution_context.proto\"\xc2\x01\n" + + "\x0eWriteInitiated\x12\x12\n" + + "\x04node\x18\x01 \x01(\tR\x04node\x12\x1c\n" + + "\tforwarder\x18\x02 \x01(\tR\tforwarder\x12\x1a\n" + + "\breceiver\x18\x03 \x01(\tR\breceiver\x12\x1b\n" + + "\treport_id\x18\x04 \x01(\rR\breportId\x12E\n" + + "\x11execution_context\x18\x14 \x01(\v2\x18.common.ExecutionContextR\x10executionContextBmZkgithub.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform;writetargetb\x06proto3" + +var ( + file_write_initiated_proto_rawDescOnce sync.Once + file_write_initiated_proto_rawDescData []byte +) + +func file_write_initiated_proto_rawDescGZIP() []byte { + file_write_initiated_proto_rawDescOnce.Do(func() { + file_write_initiated_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_write_initiated_proto_rawDesc), len(file_write_initiated_proto_rawDesc))) + }) + return file_write_initiated_proto_rawDescData +} + +var file_write_initiated_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_write_initiated_proto_goTypes = []any{ + (*WriteInitiated)(nil), // 0: platform.write_target.WriteInitiated + (*common.ExecutionContext)(nil), // 1: common.ExecutionContext +} +var file_write_initiated_proto_depIdxs = []int32{ + 1, // 0: platform.write_target.WriteInitiated.execution_context:type_name -> common.ExecutionContext + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_write_initiated_proto_init() } +func file_write_initiated_proto_init() { + if File_write_initiated_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_write_initiated_proto_rawDesc), len(file_write_initiated_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_write_initiated_proto_goTypes, + DependencyIndexes: file_write_initiated_proto_depIdxs, + MessageInfos: file_write_initiated_proto_msgTypes, + }.Build() + File_write_initiated_proto = out.File + file_write_initiated_proto_goTypes = nil + file_write_initiated_proto_depIdxs = nil +} diff --git a/capabilities/writetarget/monitoring/pb/platform/write_initiated.proto b/capabilities/writetarget/monitoring/pb/platform/write_initiated.proto new file mode 100644 index 0000000..30c4b7b --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/write_initiated.proto @@ -0,0 +1,19 @@ +syntax="proto3"; + +package platform.write_target; +option go_package = "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform;writetarget"; + +import "capabilities/writetarget/monitoring/pb/common/execution_context.proto"; + +// WT initiated the processing of the write request +message WriteInitiated { + string node = 1; + string forwarder = 2; + string receiver = 3; + + // Report Info + uint32 report_id = 4; + + // [Execution Context] + common.ExecutionContext execution_context = 20; +} diff --git a/capabilities/writetarget/monitoring/pb/platform/write_sent.pb.go b/capabilities/writetarget/monitoring/pb/platform/write_sent.pb.go new file mode 100644 index 0000000..e774c8a --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/write_sent.pb.go @@ -0,0 +1,189 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: write_sent.proto + +package writetarget + +import ( + common "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// WT sent a transaction on-chain +// +// Notice: we publish txId (TXM ref) vs. txHash (N/A here) +type WriteSent struct { + state protoimpl.MessageState `protogen:"open.v1"` + Node string `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + Forwarder string `protobuf:"bytes,2,opt,name=forwarder,proto3" json:"forwarder,omitempty"` + Receiver string `protobuf:"bytes,3,opt,name=receiver,proto3" json:"receiver,omitempty"` + // Report Info + ReportId uint32 `protobuf:"varint,4,opt,name=report_id,json=reportId,proto3" json:"report_id,omitempty"` + // Transaction ID - tx reference as created by the WT, to be used for tracking TXM execution + TxId string `protobuf:"bytes,5,opt,name=tx_id,json=txId,proto3" json:"tx_id,omitempty"` + // When was the transaction submitted + BlockData *common.BlockData `protobuf:"bytes,7,opt,name=block_data,json=blockData,proto3" json:"block_data,omitempty"` + // [Execution Context] + ExecutionContext *common.ExecutionContext `protobuf:"bytes,20,opt,name=execution_context,json=executionContext,proto3" json:"execution_context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WriteSent) Reset() { + *x = WriteSent{} + mi := &file_write_sent_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WriteSent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteSent) ProtoMessage() {} + +func (x *WriteSent) ProtoReflect() protoreflect.Message { + mi := &file_write_sent_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteSent.ProtoReflect.Descriptor instead. +func (*WriteSent) Descriptor() ([]byte, []int) { + return file_write_sent_proto_rawDescGZIP(), []int{0} +} + +func (x *WriteSent) GetNode() string { + if x != nil { + return x.Node + } + return "" +} + +func (x *WriteSent) GetForwarder() string { + if x != nil { + return x.Forwarder + } + return "" +} + +func (x *WriteSent) GetReceiver() string { + if x != nil { + return x.Receiver + } + return "" +} + +func (x *WriteSent) GetReportId() uint32 { + if x != nil { + return x.ReportId + } + return 0 +} + +func (x *WriteSent) GetTxId() string { + if x != nil { + return x.TxId + } + return "" +} + +func (x *WriteSent) GetBlockData() *common.BlockData { + if x != nil { + return x.BlockData + } + return nil +} + +func (x *WriteSent) GetExecutionContext() *common.ExecutionContext { + if x != nil { + return x.ExecutionContext + } + return nil +} + +var File_write_sent_proto protoreflect.FileDescriptor + +const file_write_sent_proto_rawDesc = "" + + "\n" + + "\x10write_sent.proto\x12\x15platform.write_target\x1aEcapabilities/writetarget/monitoring/pb/common/execution_context.proto\x1a>capabilities/writetarget/monitoring/pb/common/block_data.proto\"\x84\x02\n" + + "\tWriteSent\x12\x12\n" + + "\x04node\x18\x01 \x01(\tR\x04node\x12\x1c\n" + + "\tforwarder\x18\x02 \x01(\tR\tforwarder\x12\x1a\n" + + "\breceiver\x18\x03 \x01(\tR\breceiver\x12\x1b\n" + + "\treport_id\x18\x04 \x01(\rR\breportId\x12\x13\n" + + "\x05tx_id\x18\x05 \x01(\tR\x04txId\x120\n" + + "\n" + + "block_data\x18\a \x01(\v2\x11.common.BlockDataR\tblockData\x12E\n" + + "\x11execution_context\x18\x14 \x01(\v2\x18.common.ExecutionContextR\x10executionContextBmZkgithub.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform;writetargetb\x06proto3" + +var ( + file_write_sent_proto_rawDescOnce sync.Once + file_write_sent_proto_rawDescData []byte +) + +func file_write_sent_proto_rawDescGZIP() []byte { + file_write_sent_proto_rawDescOnce.Do(func() { + file_write_sent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_write_sent_proto_rawDesc), len(file_write_sent_proto_rawDesc))) + }) + return file_write_sent_proto_rawDescData +} + +var file_write_sent_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_write_sent_proto_goTypes = []any{ + (*WriteSent)(nil), // 0: platform.write_target.WriteSent + (*common.BlockData)(nil), // 1: common.BlockData + (*common.ExecutionContext)(nil), // 2: common.ExecutionContext +} +var file_write_sent_proto_depIdxs = []int32{ + 1, // 0: platform.write_target.WriteSent.block_data:type_name -> common.BlockData + 2, // 1: platform.write_target.WriteSent.execution_context:type_name -> common.ExecutionContext + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_write_sent_proto_init() } +func file_write_sent_proto_init() { + if File_write_sent_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_write_sent_proto_rawDesc), len(file_write_sent_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_write_sent_proto_goTypes, + DependencyIndexes: file_write_sent_proto_depIdxs, + MessageInfos: file_write_sent_proto_msgTypes, + }.Build() + File_write_sent_proto = out.File + file_write_sent_proto_goTypes = nil + file_write_sent_proto_depIdxs = nil +} diff --git a/capabilities/writetarget/monitoring/pb/platform/write_sent.proto b/capabilities/writetarget/monitoring/pb/platform/write_sent.proto new file mode 100644 index 0000000..02ba38f --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/write_sent.proto @@ -0,0 +1,28 @@ +syntax="proto3"; + +package platform.write_target; +option go_package = "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform;writetarget"; + +import "capabilities/writetarget/monitoring/pb/common/execution_context.proto"; +import "capabilities/writetarget/monitoring/pb/common/block_data.proto"; + +// WT sent a transaction on-chain +// +// Notice: we publish txId (TXM ref) vs. txHash (N/A here) +message WriteSent { + string node = 1; + string forwarder = 2; + string receiver = 3; + + // Report Info + uint32 report_id = 4; + + // Transaction ID - tx reference as created by the WT, to be used for tracking TXM execution + string tx_id = 5; + + // When was the transaction submitted + common.BlockData block_data = 7; + + // [Execution Context] + common.ExecutionContext execution_context = 20; +} diff --git a/capabilities/writetarget/monitoring/pb/platform/write_skipped.pb.go b/capabilities/writetarget/monitoring/pb/platform/write_skipped.pb.go new file mode 100644 index 0000000..6d0c6e4 --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/write_skipped.pb.go @@ -0,0 +1,172 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: write_skipped.proto + +package writetarget + +import ( + common "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// WT skipped the write request +type WriteSkipped struct { + state protoimpl.MessageState `protogen:"open.v1"` + Node string `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + Forwarder string `protobuf:"bytes,2,opt,name=forwarder,proto3" json:"forwarder,omitempty"` + Receiver string `protobuf:"bytes,3,opt,name=receiver,proto3" json:"receiver,omitempty"` + ReportId uint32 `protobuf:"varint,4,opt,name=report_id,json=reportId,proto3" json:"report_id,omitempty"` + Reason string `protobuf:"bytes,5,opt,name=reason,proto3" json:"reason,omitempty"` + // [Execution Context] + ExecutionContext *common.ExecutionContext `protobuf:"bytes,20,opt,name=execution_context,json=executionContext,proto3" json:"execution_context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WriteSkipped) Reset() { + *x = WriteSkipped{} + mi := &file_write_skipped_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WriteSkipped) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteSkipped) ProtoMessage() {} + +func (x *WriteSkipped) ProtoReflect() protoreflect.Message { + mi := &file_write_skipped_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteSkipped.ProtoReflect.Descriptor instead. +func (*WriteSkipped) Descriptor() ([]byte, []int) { + return file_write_skipped_proto_rawDescGZIP(), []int{0} +} + +func (x *WriteSkipped) GetNode() string { + if x != nil { + return x.Node + } + return "" +} + +func (x *WriteSkipped) GetForwarder() string { + if x != nil { + return x.Forwarder + } + return "" +} + +func (x *WriteSkipped) GetReceiver() string { + if x != nil { + return x.Receiver + } + return "" +} + +func (x *WriteSkipped) GetReportId() uint32 { + if x != nil { + return x.ReportId + } + return 0 +} + +func (x *WriteSkipped) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *WriteSkipped) GetExecutionContext() *common.ExecutionContext { + if x != nil { + return x.ExecutionContext + } + return nil +} + +var File_write_skipped_proto protoreflect.FileDescriptor + +const file_write_skipped_proto_rawDesc = "" + + "\n" + + "\x13write_skipped.proto\x12\x15platform.write_target\x1aEcapabilities/writetarget/monitoring/pb/common/execution_context.proto\"\xd8\x01\n" + + "\fWriteSkipped\x12\x12\n" + + "\x04node\x18\x01 \x01(\tR\x04node\x12\x1c\n" + + "\tforwarder\x18\x02 \x01(\tR\tforwarder\x12\x1a\n" + + "\breceiver\x18\x03 \x01(\tR\breceiver\x12\x1b\n" + + "\treport_id\x18\x04 \x01(\rR\breportId\x12\x16\n" + + "\x06reason\x18\x05 \x01(\tR\x06reason\x12E\n" + + "\x11execution_context\x18\x14 \x01(\v2\x18.common.ExecutionContextR\x10executionContextBmZkgithub.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform;writetargetb\x06proto3" + +var ( + file_write_skipped_proto_rawDescOnce sync.Once + file_write_skipped_proto_rawDescData []byte +) + +func file_write_skipped_proto_rawDescGZIP() []byte { + file_write_skipped_proto_rawDescOnce.Do(func() { + file_write_skipped_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_write_skipped_proto_rawDesc), len(file_write_skipped_proto_rawDesc))) + }) + return file_write_skipped_proto_rawDescData +} + +var file_write_skipped_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_write_skipped_proto_goTypes = []any{ + (*WriteSkipped)(nil), // 0: platform.write_target.WriteSkipped + (*common.ExecutionContext)(nil), // 1: common.ExecutionContext +} +var file_write_skipped_proto_depIdxs = []int32{ + 1, // 0: platform.write_target.WriteSkipped.execution_context:type_name -> common.ExecutionContext + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_write_skipped_proto_init() } +func file_write_skipped_proto_init() { + if File_write_skipped_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_write_skipped_proto_rawDesc), len(file_write_skipped_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_write_skipped_proto_goTypes, + DependencyIndexes: file_write_skipped_proto_depIdxs, + MessageInfos: file_write_skipped_proto_msgTypes, + }.Build() + File_write_skipped_proto = out.File + file_write_skipped_proto_goTypes = nil + file_write_skipped_proto_depIdxs = nil +} diff --git a/capabilities/writetarget/monitoring/pb/platform/write_skipped.proto b/capabilities/writetarget/monitoring/pb/platform/write_skipped.proto new file mode 100644 index 0000000..80e9dcb --- /dev/null +++ b/capabilities/writetarget/monitoring/pb/platform/write_skipped.proto @@ -0,0 +1,19 @@ +syntax="proto3"; + +package platform.write_target; +option go_package = "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform;writetarget"; + +import "capabilities/writetarget/monitoring/pb/common/execution_context.proto"; + +// WT skipped the write request +message WriteSkipped { + string node = 1; + string forwarder = 2; + string receiver = 3; + uint32 report_id = 4; + + string reason = 5; + + // [Execution Context] + common.ExecutionContext execution_context = 20; +} diff --git a/capabilities/writetarget/report/platform/processor/processor.go b/capabilities/writetarget/report/platform/processor/processor.go new file mode 100644 index 0000000..7e8316f --- /dev/null +++ b/capabilities/writetarget/report/platform/processor/processor.go @@ -0,0 +1,112 @@ +package processor + +import ( + "context" + "fmt" + + "google.golang.org/protobuf/proto" + + "github.com/smartcontractkit/chainlink-common/pkg/beholder" + wt "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform" + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder" +) + +const WriteTargetProcessorName = "writetarget" +const KeystoneProcessorName = "keystone" + +var PlatformDefaultProcessors = []string{WriteTargetProcessorName, KeystoneProcessorName} + +// Product-agnostic processors to be injected into WriteTarget Monitor +func NewPlatformProcessors(emitter beholder.ProtoEmitter) (map[string]beholder.ProtoProcessor, error) { + forwarderMetrics, err := forwarder.NewMetrics() + if err != nil { + return nil, fmt.Errorf("failed to create new forwarder metrics: %w", err) + } + + wtMetrics, err := wt.NewMetrics() + if err != nil { + return nil, fmt.Errorf("failed to create new write target metrics: %w", err) + } + return map[string]beholder.ProtoProcessor{ + "keystone": &keystoneProcessor{ + emitter: emitter, + metrics: forwarderMetrics, + }, + "writetarget": &wtProcessor{ + metrics: wtMetrics, + }, + }, nil +} + +// Write-Target specific processor decodes write messages to derive metrics +type wtProcessor struct { + metrics *wt.Metrics +} + +func (p *wtProcessor) Process(ctx context.Context, m proto.Message, attrKVs ...any) error { + // Switch on the type of the proto.Message + switch msg := m.(type) { + case *wt.WriteInitiated: + err := p.metrics.OnWriteInitiated(ctx, msg, attrKVs...) + if err != nil { + return fmt.Errorf("failed to publish write initiated metrics: %w", err) + } + return nil + case *wt.WriteError: + err := p.metrics.OnWriteError(ctx, msg, attrKVs...) + if err != nil { + return fmt.Errorf("failed to publish write error metrics: %w", err) + } + return nil + case *wt.WriteSent: + err := p.metrics.OnWriteSent(ctx, msg, attrKVs...) + if err != nil { + return fmt.Errorf("failed to publish write sent metrics: %w", err) + } + return nil + case *wt.WriteConfirmed: + err := p.metrics.OnWriteConfirmed(ctx, msg, attrKVs...) + if err != nil { + return fmt.Errorf("failed to publish write confirmed metrics: %w", err) + } + return nil + default: + return nil // fallthrough + } +} + +// Keystone specific processor decodes writes as 'platform.forwarder.ReportProcessed' messages + metrics +type keystoneProcessor struct { + emitter beholder.ProtoEmitter + metrics *forwarder.Metrics +} + +func (p *keystoneProcessor) Process(ctx context.Context, m proto.Message, attrKVs ...any) error { + // Switch on the type of the proto.Message + switch msg := m.(type) { + case *wt.WriteConfirmed: + // TODO: detect the type of write payload (support more than one type of write, first multiple Keystone report versions) + // https://smartcontract-it.atlassian.net/browse/NONEVM-817 + // Q: Will this msg ever contain different (non-Keystone) types of writes? Hmm. + // Notice: we assume all writes are Keystone (v1) writes for now + + // Decode as a 'platform.forwarder.ReportProcessed' message + reportProcessed, err := forwarder.DecodeAsReportProcessed(msg) + if err != nil { + return fmt.Errorf("failed to decode as 'platform.forwarder.ReportProcessed': %w", err) + } + // Emit the 'platform.forwarder.ReportProcessed' message + err = p.emitter.EmitWithLog(ctx, reportProcessed, attrKVs...) + if err != nil { + return fmt.Errorf("failed to emit with log: %w", err) + } + // Process emit and derive metrics + err = p.metrics.OnReportProcessed(ctx, reportProcessed, attrKVs...) + if err != nil { + return fmt.Errorf("failed to publish report processed metrics: %w", err) + } + return nil + default: + return nil // fallthrough + } +} diff --git a/capabilities/writetarget/report/platform/types.go b/capabilities/writetarget/report/platform/types.go new file mode 100644 index 0000000..1b9a7a4 --- /dev/null +++ b/capabilities/writetarget/report/platform/types.go @@ -0,0 +1,35 @@ +package platform + +import ( + "fmt" + + ocr3types "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3/types" +) + +// Report represents an OCR3 report with metadata and data +// version | workflow_execution_id | timestamp | don_id | config_version | ... | data +type Report struct { + ocr3types.Metadata + Data []byte +} + +func Decode(raw []byte) (*Report, error) { + md, tail, err := ocr3types.Decode(raw) + if err != nil { + return nil, err + } + if md.Version != 1 { + return nil, fmt.Errorf("unsupported version %d", md.Version) + } + return &Report{Metadata: md, Data: tail}, nil +} + +func (r Report) Encode() ([]byte, error) { + // Encode the metadata + metadataBytes, err := r.Metadata.Encode() + if err != nil { + return nil, err + } + + return append(metadataBytes, r.Data...), nil +} diff --git a/capabilities/writetarget/report/platform/types_test.go b/capabilities/writetarget/report/platform/types_test.go new file mode 100644 index 0000000..92ba361 --- /dev/null +++ b/capabilities/writetarget/report/platform/types_test.go @@ -0,0 +1,38 @@ +package platform + +import ( + "encoding/base64" + "encoding/hex" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDecodeReport(t *testing.T) { + // Base64-encoded report data (example) + // version | workflow_execution_id | timestamp | don_id | config_version | ... | data + encoded := "AYFtgPpLuLNQysw6LjlSNrzGuBOwVoth7qC9PmunIY3TZvW/cAAAAAEAAAABvAbzAOeX1ahXVjehSq4T4/hQgAjR/FT0xGEf/xemjLAwMDAwRk9PQkFSAAAAAAAAAAAAAAAAAAAAAAAAAKoAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAMREREREREREQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgAAMREREREREREQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZvW/aQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABm9b9pAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAElCUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASUJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABnBQGpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAElCUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASUJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABJQlAAMiIiIiIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgAAMiIiIiIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZvW/aQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABm9b9pAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAElCUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASUJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABnBQGpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAElCUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASUJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABJQl" + + // Decode the base64 data + rawReport, err := base64.StdEncoding.DecodeString(encoded) + require.NoError(t, err) + + r, err := Decode(rawReport) + require.NoError(t, err) + require.Equal(t, uint32(1), r.Version) + require.Equal(t, "816d80fa4bb8b350cacc3a2e395236bcc6b813b0568b61eea0bd3e6ba7218dd3", r.ExecutionID) + require.Equal(t, uint32(1727381360), r.Timestamp) + require.Equal(t, uint32(0x01), r.DONID) + require.Equal(t, uint32(0x01), r.DONConfigVersion) + + require.Equal(t, "bc06f300e797d5a8575637a14aae13e3f8508008d1fc54f4c4611fff17a68cb0", r.WorkflowID) + require.Equal(t, "30303030464f4f424152", r.WorkflowName) + + workflowName, err := hex.DecodeString(r.WorkflowName) + require.NoError(t, err) + + require.Equal(t, "0000FOOBAR", string(workflowName)) + + require.Equal(t, "00000000000000000000000000000000000000aa", r.WorkflowOwner) + require.Equal(t, "0001", r.ReportID) +} diff --git a/capabilities/writetarget/write_target.go b/capabilities/writetarget/write_target.go new file mode 100644 index 0000000..386b6dc --- /dev/null +++ b/capabilities/writetarget/write_target.go @@ -0,0 +1,502 @@ +//nolint:gosec,revive // disable G115,revive +package writetarget + +import ( + "context" + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + + "github.com/smartcontractkit/chainlink-common/pkg/beholder" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + + monitor "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/beholder" + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/report/platform" + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/retry" + + wt "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform" +) + +var ( + _ capabilities.ExecutableCapability = &writeTarget{} +) + +type TransactionStatus uint8 + +// new chain agnostic transmission state types +const ( + TransmissionStateNotAttempted TransactionStatus = iota + TransmissionStateSucceeded + TransmissionStateFailed // retry + TransmissionStateFatal // don't retry +) + +// alter TransmissionState to reference specific types rather than just +// success bool +type TransmissionState struct { + Status TransactionStatus + Transmitter string + Err error +} + +type TargetStrategy interface { + // QueryTransmissionState defines how the report should be queried + // via ChainReader, and how resulting errors should be classified. + QueryTransmissionState(ctx context.Context, reportID uint16, request capabilities.CapabilityRequest) (*TransmissionState, error) + // TransmitReport constructs the tx to transmit the report, and defines + // any specific handling for sending the report via ChainWriter. + TransmitReport(ctx context.Context, report []byte, reportContext []byte, signatures [][]byte, request capabilities.CapabilityRequest) (string, error) + // Wrapper around the ChainWriter to get the transaction status + GetTransactionStatus(ctx context.Context, transactionID string) (commontypes.TransactionStatus, error) +} + +var ( + _ capabilities.ExecutableCapability = &writeTarget{} +) + +// chain-agnostic consts +const ( + CapabilityName = "write" + + // Input keys + // Is this key chain agnostic? + KeySignedReport = "signed_report" +) + +type chainService interface { + LatestHead(ctx context.Context) (commontypes.Head, error) +} + +type writeTarget struct { + capabilities.CapabilityInfo + + config Config + chainInfo monitor.ChainInfo + + lggr logger.Logger + // Local beholder client, also hosting the protobuf emitter + beholder *beholder.BeholderClient + + cs chainService + configValidateFn func(request capabilities.CapabilityRequest) (string, error) + + nodeAddress string + forwarderAddress string + + targetStrategy TargetStrategy + writeAcceptanceState commontypes.TransactionStatus +} +type WriteTargetOpts struct { + ID string + + // toml: [.WriteTargetCap] + Config Config + // ChainInfo contains the chain information (used as execution context) + // TODO: simplify by passing via ChainService.GetChainStatus fn + ChainInfo monitor.ChainInfo + + Logger logger.Logger + Beholder *beholder.BeholderClient + + ChainService chainService + ConfigValidateFn func(request capabilities.CapabilityRequest) (string, error) + + NodeAddress string + ForwarderAddress string + + TargetStrategy TargetStrategy + WriteAcceptanceState commontypes.TransactionStatus +} + +// Capability-specific configuration +type ReqConfig struct { + Address string + Processor string +} + +// NewWriteTargetID returns the capability ID for the write target +func NewWriteTargetID(chainFamilyName, networkName, chainID, version string) (string, error) { + // Input args should not be empty + if version == "" { + return "", fmt.Errorf("version must not be empty") + } + + // Network ID: network name is optional, if not provided, use the chain ID + networkID := networkName + if networkID == "" && chainID == "" { + return "", fmt.Errorf("invalid input: networkName or chainID must not be empty") + } + if networkID == "" || networkID == "unknown" { + networkID = chainID + } + + // allow for chain family to be empty + if chainFamilyName == "" { + return fmt.Sprintf("%s_%s@%s", CapabilityName, networkID, version), nil + } + + return fmt.Sprintf("%s_%s-%s@%s", CapabilityName, chainFamilyName, networkID, version), nil +} + +// TODO: opts.Config input is not validated for sanity +func NewWriteTarget(opts WriteTargetOpts) capabilities.ExecutableCapability { + capInfo := capabilities.MustNewCapabilityInfo(opts.ID, capabilities.CapabilityTypeTarget, CapabilityName) + + // override Unknown to ensure Finalized is default + if opts.WriteAcceptanceState == 0 { + opts.WriteAcceptanceState = commontypes.Finalized + } + + return &writeTarget{ + capInfo, + opts.Config, + opts.ChainInfo, + opts.Logger, + opts.Beholder, + opts.ChainService, + opts.ConfigValidateFn, + opts.NodeAddress, + opts.ForwarderAddress, + opts.TargetStrategy, + opts.WriteAcceptanceState, + } +} + +func success() capabilities.CapabilityResponse { + return capabilities.CapabilityResponse{} +} + +func (c *writeTarget) Execute(ctx context.Context, request capabilities.CapabilityRequest) (capabilities.CapabilityResponse, error) { + // Take the local timestamp + tsStart := time.Now().UnixMilli() + + // Trace the execution + attrs := c.traceAttributes(request.Metadata.WorkflowExecutionID) + ctx, span := c.beholder.Tracer.Start(ctx, "Execute", trace.WithAttributes(attrs...)) + defer span.End() + + // Notice: error skipped as implementation always returns nil + capInfo, _ := c.Info(ctx) + + c.lggr.Debugw("Execute", "request", request, "capInfo", capInfo) + + // Helper to keep track of the request info + info := requestInfo{ + tsStart: tsStart, + node: c.nodeAddress, + forwarder: c.forwarderAddress, + receiver: "N/A", + request: request, + reportInfo: &reportInfo{ + reportContext: nil, + report: nil, + signersNum: 0, // N/A + reportID: 0, // N/A + }, + reportTransmissionState: nil, + } + // Helper to build monitoring (Beholder) messages + builder := NewMessageBuilder(c.chainInfo, capInfo) + + // Validate the config + receiver, err := c.configValidateFn(request) + if err != nil { + msg := builder.buildWriteError(info, 0, "failed to validate config", err.Error()) + return capabilities.CapabilityResponse{}, c.asEmittedError(ctx, msg) + } + + // Source the receiver address from the config + info.receiver = receiver + + // Source the signed report from the request + signedReport, ok := request.Inputs.Underlying[KeySignedReport] + if !ok { + cause := fmt.Sprintf("input missing required field: '%s'", KeySignedReport) + msg := builder.buildWriteError(info, 0, "failed to source the signed report", cause) + return capabilities.CapabilityResponse{}, c.asEmittedError(ctx, msg) + } + + // Decode the signed report + inputs := types.SignedReport{} + if err = signedReport.UnwrapTo(&inputs); err != nil { + msg := builder.buildWriteError(info, 0, "failed to parse signed report", err.Error()) + return capabilities.CapabilityResponse{}, c.asEmittedError(ctx, msg) + } + + // Source the report ID from the input + info.reportInfo.reportID = binary.BigEndian.Uint16(inputs.ID) + + err = c.beholder.ProtoEmitter.EmitWithLog(ctx, builder.buildWriteInitiated(info)) + if err != nil { + c.lggr.Errorw("failed to emit write initiated", "err", err) + } + + // Check whether the report is valid (e.g., not empty) + if len(inputs.Report) == 0 { + // We received any empty report -- this means we should skip transmission. + err = c.beholder.ProtoEmitter.EmitWithLog(ctx, builder.buildWriteSkipped(info, "empty report")) + if err != nil { + c.lggr.Errorw("failed to emit write skipped", "err", err) + } + return success(), nil + } + + // Update the info with the report info + info.reportInfo = &reportInfo{ + reportID: info.reportInfo.reportID, + reportContext: inputs.Context, + report: inputs.Report, + signersNum: uint32(len(inputs.Signatures)), + } + + // Decode the report + reportDecoded, err := platform.Decode(inputs.Report) + if err != nil { + msg := builder.buildWriteError(info, 0, "failed to decode the report", err.Error()) + return capabilities.CapabilityResponse{}, c.asEmittedError(ctx, msg) + } + + // Validate encoded report is prefixed with workflowID and executionID that match the request meta + if reportDecoded.ExecutionID != request.Metadata.WorkflowExecutionID { + msg := builder.buildWriteError(info, 0, "decoded report execution ID does not match the request", "") + return capabilities.CapabilityResponse{}, c.asEmittedError(ctx, msg) + } else if reportDecoded.WorkflowID != request.Metadata.WorkflowID { + msg := builder.buildWriteError(info, 0, "decoded report workflow ID does not match the request", "") + return capabilities.CapabilityResponse{}, c.asEmittedError(ctx, msg) + } + + // Fetch the latest head from the chain (timestamp), retry with a default backoff strategy + ctx = retry.CtxWithID(ctx, info.request.Metadata.WorkflowExecutionID) + head, err := retry.With(ctx, c.lggr, c.cs.LatestHead) + if err != nil { + msg := builder.buildWriteError(info, 0, "failed to fetch the latest head", err.Error()) + return capabilities.CapabilityResponse{}, c.asEmittedError(ctx, msg) + } + + c.lggr.Debugw("non-empty valid report", + "reportID", info.reportInfo.reportID, + "report", "0x"+hex.EncodeToString(inputs.Report), + "reportLen", len(inputs.Report), + "reportDecoded", reportDecoded, + "reportContext", "0x"+hex.EncodeToString(inputs.Context), + "reportContextLen", len(inputs.Context), + "signaturesLen", len(inputs.Signatures), + "executionID", request.Metadata.WorkflowExecutionID, + ) + + state, err := c.targetStrategy.QueryTransmissionState(ctx, info.reportInfo.reportID, request) + + if err != nil { + msg := builder.buildWriteError(info, 0, "failed to fetch [TransmissionState]", err.Error()) + return capabilities.CapabilityResponse{}, c.asEmittedError(ctx, msg) + } + + switch state.Status { + case TransmissionStateNotAttempted: + c.lggr.Debugw("Report is not on chain yet, pushing tx", "reportID", info.reportInfo.reportID) + case TransmissionStateFailed: + c.lggr.Debugw("Tranmissions previously failed, retrying", "reportID", info.reportInfo.reportID) + case TransmissionStateFatal: + msg := builder.buildWriteError(info, 0, "Transmission attempt fatal", state.Err.Error()) + return capabilities.CapabilityResponse{}, c.asEmittedError(ctx, msg) + case TransmissionStateSucceeded: + // Source the transmitter address from the on-chain state + info.reportTransmissionState = state + + err = c.beholder.ProtoEmitter.EmitWithLog(ctx, builder.buildWriteConfirmed(info, head)) + if err != nil { + c.lggr.Errorw("failed to emit write confirmed", "err", err) + } + return success(), nil + } + + c.lggr.Infow("on-chain report check done - attempting to push to txmgr", + "reportID", info.reportInfo.reportID, + "reportLen", len(inputs.Report), + "reportContextLen", len(inputs.Context), + "signaturesLen", len(inputs.Signatures), + "executionID", request.Metadata.WorkflowExecutionID, + ) + + txID, err := c.targetStrategy.TransmitReport(ctx, inputs.Report, inputs.Context, inputs.Signatures, request) + c.lggr.Debugw("Transaction submitted", "request", request, "transaction-id", txID) + if err != nil { + msg := builder.buildWriteError(info, 0, "failed to transmit the report", err.Error()) + return capabilities.CapabilityResponse{}, c.asEmittedError(ctx, msg) + } + err = c.beholder.ProtoEmitter.EmitWithLog(ctx, builder.buildWriteSent(info, head, txID)) + if err != nil { + c.lggr.Errorw("failed to emit write sent", "err", err) + } + + err = c.acceptAndConfirmWrite(ctx, info, txID) + if err != nil { + return capabilities.CapabilityResponse{}, err + } + return success(), nil +} + +func (c *writeTarget) RegisterToWorkflow(ctx context.Context, request capabilities.RegisterToWorkflowRequest) error { + // TODO: notify the background WriteTxConfirmer (workflow registered) + return nil +} + +func (c *writeTarget) UnregisterFromWorkflow(ctx context.Context, request capabilities.UnregisterFromWorkflowRequest) error { + // TODO: notify the background WriteTxConfirmer (workflow unregistered) + return nil +} + +// acceptAndConfirmWrite waits (until timeout) for the report to be accepted and (optionally) confirmed on-chain +// Emits Beholder messages: +// - 'platform.write-target.WriteError' if not accepted +// - 'platform.write-target.WriteAccepted' if accepted (with or without an error) +// - 'platform.write-target.WriteError' if accepted (with an error) +// - 'platform.write-target.WriteConfirmed' if confirmed (until timeout) +func (c *writeTarget) acceptAndConfirmWrite(ctx context.Context, info requestInfo, txID string) error { + attrs := c.traceAttributes(info.request.Metadata.WorkflowExecutionID) + _, span := c.beholder.Tracer.Start(ctx, "Execute.acceptAndConfirmWrite", trace.WithAttributes(attrs...)) + defer span.End() + + lggr := logger.Named(c.lggr, "write-confirmer") + + // Timeout for the confirmation process + timeout := c.config.AcceptanceTimeout + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + // Retry interval for the confirmation process + interval := c.config.PollPeriod + ticker := services.NewTicker(interval) + defer ticker.Stop() + + // Helper to build monitoring (Beholder) messages + // Notice: error skipped as implementation always returns nil + capInfo, _ := c.Info(ctx) + builder := NewMessageBuilder(c.chainInfo, capInfo) + + txAccepted, err := c.waitTxReachesTerminalStatus(ctx, lggr, txID) + + if err != nil { + // We (eventually) failed to confirm the report was transmitted + msg := builder.buildWriteError(info, 0, "failed to wait until tx gets finalized", err.Error()) + lggr.Errorw("failed to wait until tx gets finalized", "txID", txID, "error", err) + return c.asEmittedError(ctx, msg) + } + + for { + select { + case <-ctx.Done(): + // We (eventually) failed to confirm the report was transmitted + cause := "transaction was finalized, but report was not observed on chain before timeout" + if !txAccepted { + cause = "transaction failed and no other node managed to get report on chain before timeout" + } + msg := builder.buildWriteError(info, 0, "write confirmation - failed", cause) + return c.asEmittedError(ctx, msg) + case <-ticker.C: + // Fetch the latest head from the chain (timestamp) + head, err := c.cs.LatestHead(ctx) + if err != nil { + lggr.Errorw("failed to fetch the latest head", "txID", txID, "err", err) + continue + } + + // Check confirmation status (transmission state) + state, err := c.targetStrategy.QueryTransmissionState(ctx, info.reportInfo.reportID, info.request) + if err != nil { + lggr.Errorw("failed to check confirmed status", "txID", txID, "err", err) + continue + } + + if state == nil || state.Status == TransmissionStateNotAttempted { + lggr.Infow("not confirmed yet - transmission state NOT visible", "txID", txID) + continue + } + + // We (eventually) confirmed the report was transmitted + // Emit the confirmation message and return + if !txAccepted { + lggr.Infow("confirmed - transmission state visible but submitted by another node. This node's tx failed", "txID", txID) + } else { + lggr.Infow("confirmed - transmission state visible", "txID", txID) + } + + // Source the transmitter address from the on-chain state + info.reportTransmissionState = state + + _ = c.beholder.ProtoEmitter.EmitWithLog(ctx, builder.buildWriteConfirmed(info, head)) + + return nil + } + } +} + +// Polls transaction status until it reaches one of terminal states [Finalized, Failed, Fatal] +func (c *writeTarget) waitTxReachesTerminalStatus(ctx context.Context, lggr logger.Logger, txID string) (finalized bool, err error) { + // Retry interval for the confirmation process + interval := c.config.PollPeriod + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return false, ctx.Err() + case <-ticker.C: + // Check TXM for status + status, err := c.targetStrategy.GetTransactionStatus(ctx, txID) + if err != nil { + lggr.Errorw("failed to fetch the transaction status", "txID", txID, "err", err) + continue + } + + lggr.Debugw("txm - tx status", "txID", txID, "status", status) + + switch status { + case commontypes.Failed, commontypes.Fatal: + // TODO: [Beholder] Emit 'platform.write-target.WriteError' if accepted with an error (surface specific on-chain error) + lggr.Infow("transaction failed", "txID", txID, "status", status) + return false, nil + default: + // Notice: On slower chains we can accept a non-finalized state, but on faster chains we should always wait for finalization. + if status >= c.writeAcceptanceState { + // Notice: report write confirmation is only possible after a tx is accepted without an error + // TODO: [Beholder] Emit 'platform.write-target.WriteAccepted' (useful to source tx hash, block number, and tx status/error) + lggr.Infow("accepted", "txID", txID, "status", status) + return true, nil + } else { + lggr.Infow("not accepted yet", "txID", txID, "status", status) + continue + } + } + } + } +} + +// traceAttributes returns the attributes to be used for tracing +func (c *writeTarget) traceAttributes(workflowExecutionID string) []attribute.KeyValue { + return []attribute.KeyValue{ + attribute.String("capability_id", c.ID), + attribute.String("capability_type", string(c.CapabilityType)), + attribute.String("workflow_execution_id", workflowExecutionID), + } +} + +// asEmittedError returns the WriteError message as an (Go) error, after emitting it first +func (c *writeTarget) asEmittedError(ctx context.Context, e *wt.WriteError, attrKVs ...any) error { + // Notice: we always want to log the error + err := c.beholder.ProtoEmitter.EmitWithLog(ctx, e, attrKVs...) + if err != nil { + return errors.Join(fmt.Errorf("failed to emit error: %+w", err), e) + } + return e +} diff --git a/capabilities/writetarget/write_target_monitor.go b/capabilities/writetarget/write_target_monitor.go new file mode 100644 index 0000000..286a710 --- /dev/null +++ b/capabilities/writetarget/write_target_monitor.go @@ -0,0 +1,122 @@ +package writetarget + +import ( + "context" + "fmt" + + "google.golang.org/protobuf/proto" + + "github.com/smartcontractkit/chainlink-common/pkg/beholder" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + wt "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform" +) + +const ( + repoCLLCommon = "https://raw.githubusercontent.com/smartcontractkit/chainlink-framework" + // TODO: replace with main when merged + versionRefsDevelop = "refs/heads/refactor-write-target" + schemaBasePath = repoCLLCommon + "/" + versionRefsDevelop + "/capabilities/capabilities/writetarget/pb" +) + +func NewMonitorEmitter(lggr logger.Logger) beholder.ProtoEmitter { + // Initialize the Beholder client with a local logger a custom Emitter + client := beholder.GetClient().ForPackage("write_target") + return beholder.NewProtoEmitter(lggr, &client, schemaBasePath) +} + +type MonitorOpts struct { + Lggr logger.Logger + Processors map[string]beholder.ProtoProcessor + EnabledProcessors []string + Emitter beholder.ProtoEmitter +} + +// NewMonitor initializes a Beholder client for the Write Target +// +// The client is initialized as a BeholderClient extension with a custom ProtoEmitter. +// The ProtoEmitter is proxied with additional processing for emitted messages. This processing +// includes decoding messages as specific types and deriving metrics based on the decoded messages. +// TODO: Report decoding uses the same ABI for EVM and Aptos, however, future chains may need a different +// decoding scheme. Generalize this in the future to support different chains and decoding schemes. +func NewMonitor(opts MonitorOpts) (*beholder.BeholderClient, error) { + client := beholder.GetClient().ForPackage("write_target") + + // Proxy ProtoEmitter with additional processing + protoEmitterProxy := protoEmitter{ + lggr: opts.Lggr, + emitter: opts.Emitter, + processors: opts.Processors, + enabledProcessors: opts.EnabledProcessors, + } + return &beholder.BeholderClient{Client: &client, ProtoEmitter: &protoEmitterProxy}, nil +} + +// ProtoEmitter proxy specific to the WT +type protoEmitter struct { + lggr logger.Logger + emitter beholder.ProtoEmitter + processors map[string]beholder.ProtoProcessor + enabledProcessors []string +} + +// Emit emits a proto.Message and runs additional processing +func (e *protoEmitter) Emit(ctx context.Context, m proto.Message, attrKVs ...any) error { + err := e.emitter.Emit(ctx, m, attrKVs...) + if err != nil { + return fmt.Errorf("failed to emit: %w", err) + } + + // Notice: we skip processing errors (and continue) so this will never error + return e.Process(ctx, m, attrKVs...) +} + +// EmitWithLog emits a proto.Message and runs additional processing +func (e *protoEmitter) EmitWithLog(ctx context.Context, m proto.Message, attrKVs ...any) error { + err := e.emitter.EmitWithLog(ctx, m, attrKVs...) + if err != nil { + return fmt.Errorf("failed to emit with log: %w", err) + } + + // Notice: we skip processing errors (and continue) so this will never error + return e.Process(ctx, m, attrKVs...) +} + +// Process aggregates further processing for emitted messages +func (e *protoEmitter) Process(ctx context.Context, m proto.Message, attrKVs ...any) error { + // Further processing for emitted messages + for _, processorName := range e.enabledProcessors { + p, ok := e.processors[processorName] + if !ok { + // no processor matching configured one, log error but continue + e.lggr.Errorf("no required processor with name %s", processorName) + continue + } + err := p.Process(ctx, m, attrKVs...) + if err != nil { + // Notice: we swallow and log processing errors + // These should be investigated and fixed, but are not critical to product runtime, + // and shouldn't block further processing of the emitted message. + e.lggr.Errorw("failed to process emitted message", "err", err) + return nil + } + } + // Product specific processing only for write confirmed + if msg, ok := m.(*wt.WriteConfirmed); ok { + if msg.MetaCapabilityProcessor == "" { + e.lggr.Debugw("No product specific processor specified; skipping.") + return nil + } + + if p, ok := e.processors[msg.MetaCapabilityProcessor]; ok { + if err := p.Process(ctx, msg, attrKVs...); err != nil { + e.lggr.Errorw("failed to process emitted message", "err", err) + } + } else { + // no processor matching configured one, log error + e.lggr.Errorf("no matching processor for MetaCapabilityProcessor=%s", msg.MetaCapabilityProcessor) + } + } + + return nil +} diff --git a/capabilities/writetarget/write_target_monitor_test.go b/capabilities/writetarget/write_target_monitor_test.go new file mode 100644 index 0000000..16b2730 --- /dev/null +++ b/capabilities/writetarget/write_target_monitor_test.go @@ -0,0 +1,71 @@ +package writetarget_test + +import ( + "testing" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink-common/pkg/beholder" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget" + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/beholder/mocks" + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/common" + wt "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform" +) + +func TestWriteTargetMonitor(t *testing.T) { + processor := mocks.NewProtoProcessor(t) + lggr, observed := logger.TestObserved(t, zapcore.DebugLevel) + + m, err := writetarget.NewMonitor(writetarget.MonitorOpts{ + lggr, + map[string]beholder.ProtoProcessor{"test": processor}, + []string{}, + writetarget.NewMonitorEmitter(lggr), + }) + require.NoError(t, err) + + encoded := []byte{} + + msg := &wt.WriteConfirmed{ + BlockData: &common.BlockData{ + BlockHeight: "10", + }, + MetaCapabilityProcessor: "test", + Report: encoded, + } + + processor.On("Process", t.Context(), msg, mock.Anything).Return(nil).Once() + err = m.ProtoEmitter.EmitWithLog(t.Context(), msg) + require.NoError(t, err) + + m, err = writetarget.NewMonitor(writetarget.MonitorOpts{lggr, map[string]beholder.ProtoProcessor{"other": processor}, []string{}, writetarget.NewMonitorEmitter(lggr)}) + require.NoError(t, err) + + err = m.ProtoEmitter.EmitWithLog(t.Context(), msg) + require.NoError(t, err) + + tests.RequireLogMessage(t, observed, "no matching processor for MetaCapabilityProcessor=test") + + // get new processor + processor = mocks.NewProtoProcessor(t) + msg.MetaCapabilityProcessor = "" + processor.AssertNotCalled(t, "Process", mock.Anything, mock.Anything, mock.Anything) + + err = m.ProtoEmitter.EmitWithLog(t.Context(), msg) + require.NoError(t, err) + + tests.RequireLogMessage(t, observed, "No product specific processor specified; skipping.") + + m, err = writetarget.NewMonitor(writetarget.MonitorOpts{lggr, map[string]beholder.ProtoProcessor{}, []string{"other"}, writetarget.NewMonitorEmitter(lggr)}) + require.NoError(t, err) + + err = m.ProtoEmitter.EmitWithLog(t.Context(), msg) + require.NoError(t, err) + + tests.RequireLogMessage(t, observed, "no required processor with name other") +} diff --git a/capabilities/writetarget/write_target_test.go b/capabilities/writetarget/write_target_test.go new file mode 100644 index 0000000..00b23d1 --- /dev/null +++ b/capabilities/writetarget/write_target_test.go @@ -0,0 +1,427 @@ +package writetarget_test + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink-common/pkg/beholder" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink-common/pkg/values" + + writetarget "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget" + monitor "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/beholder" + utils "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/monitoring/pb/platform/on-chain/forwarder" + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/report/platform" + "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/report/platform/processor" + + monmocks "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/beholder/mocks" + wtmocks "github.com/smartcontractkit/chainlink-framework/capabilities/writetarget/mocks" +) + +// setupWriteTarget builds the WriteTarget and a matching CapabilityRequest with a valid signed report +func setupWriteTarget( + t *testing.T, + lggr logger.Logger, + strategy *wtmocks.TargetStrategy, + chainSvc *wtmocks.ChainService, + productSpecificProcessor bool, + emitter beholder.ProtoEmitter, +) (capabilities.ExecutableCapability, capabilities.CapabilityRequest) { + platformProcessors, err := processor.NewPlatformProcessors(emitter) + require.NoError(t, err) + + if productSpecificProcessor { + platformProcessors["test"] = newMockProductSpecificProcessor(t) + } + monClient, err := writetarget.NewMonitor(writetarget.MonitorOpts{lggr, platformProcessors, processor.PlatformDefaultProcessors, emitter}) + require.NoError(t, err) + + pollPeriod := 100 * time.Millisecond + + timeout := 500 * time.Millisecond + + opts := writetarget.WriteTargetOpts{ + ID: "write_generic-testnet@1.0.0", + Config: writetarget.Config{ + PollPeriod: pollPeriod, + AcceptanceTimeout: timeout, + }, + ChainInfo: monitor.ChainInfo{}, + Logger: lggr, + Beholder: monClient, + ChainService: chainSvc, + ConfigValidateFn: func(_ capabilities.CapabilityRequest) (string, error) { return "0xreceiver", nil }, + NodeAddress: "0xnode", + ForwarderAddress: "0xforwarder", + TargetStrategy: strategy, + WriteAcceptanceState: commontypes.Finalized, + } + wt := writetarget.NewWriteTarget(opts) + + // generate a valid on-chain report and metadata + report, err := utils.NewTestReport(t, []byte{}) + require.NoError(t, err) + reportCtx := []byte{42} + sigs := [][]byte{{1, 2, 3}} + + inputs, err := values.NewMap(map[string]any{ + "signed_report": map[string]any{ + "id": report[:2], + "report": report, + "context": reportCtx, + "signatures": sigs, + }, + }) + require.NoError(t, err) + + req := capabilities.CapabilityRequest{Inputs: inputs} + + // match decoded report to request metadata + repDecoded, err := platform.Decode(report) + require.NoError(t, err) + req.Metadata = capabilities.RequestMetadata{ + WorkflowID: repDecoded.WorkflowID, + WorkflowOwner: repDecoded.WorkflowOwner, + WorkflowName: repDecoded.WorkflowName, + WorkflowExecutionID: repDecoded.ExecutionID, + } + + cfg, err := values.NewMap(map[string]any{"address": "0x1", "processor": "test"}) + require.NoError(t, err) + req.Config = cfg + + return wt, req +} + +func newMockProductSpecificProcessor(t *testing.T) beholder.ProtoProcessor { + processor := monmocks.NewProtoProcessor(t) + processor.EXPECT().Process(mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + return processor +} + +type testCase struct { + name string + initialTransmissionState writetarget.TransmissionState + txState commontypes.TransactionStatus + simulateTxError bool + expectError bool + errorContains string + productSpecificProcessor bool + requiredLogMessage string +} + +func TestWriteTarget_Execute(t *testing.T) { + cases := []testCase{ + { + name: "succeeds transmission state is not attempted", + initialTransmissionState: writetarget.TransmissionState{Status: writetarget.TransmissionStateNotAttempted}, + txState: commontypes.Finalized, + expectError: false, + requiredLogMessage: "no matching processor for MetaCapabilityProcessor=test", + }, + { + name: "succeeds transmission state is already succeeded", + initialTransmissionState: writetarget.TransmissionState{Status: writetarget.TransmissionStateSucceeded}, + txState: commontypes.Unknown, + expectError: false, + }, + { + name: "succeeds transmission state is not attempted with product specific processor", + initialTransmissionState: writetarget.TransmissionState{Status: writetarget.TransmissionStateNotAttempted}, + txState: commontypes.Finalized, + expectError: false, + productSpecificProcessor: true, + requiredLogMessage: "confirmed - transmission state visible", + }, + { + name: "already succeeded with product specific processor", + initialTransmissionState: writetarget.TransmissionState{Status: writetarget.TransmissionStateSucceeded}, + txState: commontypes.Unknown, + expectError: false, + productSpecificProcessor: true, + }, + { + name: "fatal initialTransmissionState", + initialTransmissionState: writetarget.TransmissionState{Status: writetarget.TransmissionStateFatal, Err: errors.New("fatal")}, + txState: commontypes.Unknown, + expectError: true, + errorContains: "Transmission attempt fatal", + }, + { + name: "transmit error", + initialTransmissionState: writetarget.TransmissionState{Status: writetarget.TransmissionStateNotAttempted}, + txState: commontypes.Pending, + simulateTxError: true, + expectError: true, + errorContains: "failed to transmit the report", + }, + { + name: "Returns error if tx is not finalized before timeout", + initialTransmissionState: writetarget.TransmissionState{Status: writetarget.TransmissionStateNotAttempted}, + txState: commontypes.Pending, + simulateTxError: false, + expectError: true, + errorContains: "context deadline exceeded", + }, + { + name: "Returns error if tx is finalized but no report is on-chain", + initialTransmissionState: writetarget.TransmissionState{Status: writetarget.TransmissionStateNotAttempted}, + txState: commontypes.Finalized, + simulateTxError: false, + expectError: true, + errorContains: "platform.write_target.WriteError [ERR-0] - write confirmation - failed: transaction was finalized, but report was not observed on chain before timeout", + }, + { + name: "Returns error if tx is fatal but no report is on-chain", + initialTransmissionState: writetarget.TransmissionState{Status: writetarget.TransmissionStateNotAttempted}, + txState: commontypes.Fatal, + simulateTxError: false, + expectError: true, + errorContains: "platform.write_target.WriteError [ERR-0] - write confirmation - failed: transaction failed and no other node managed to get report on chain before timeout", + }, + { + name: "Returns error if tx is failed but no report is on-chain", + initialTransmissionState: writetarget.TransmissionState{Status: writetarget.TransmissionStateNotAttempted}, + txState: commontypes.Failed, + simulateTxError: false, + expectError: true, + errorContains: "platform.write_target.WriteError [ERR-0] - write confirmation - failed: transaction failed and no other node managed to get report on chain before timeout", + }, + { + name: "Returns success if report is on-chain but tx is fatal", + initialTransmissionState: writetarget.TransmissionState{Status: writetarget.TransmissionStateNotAttempted}, + txState: commontypes.Fatal, + simulateTxError: false, + expectError: false, + requiredLogMessage: "confirmed - transmission state visible but submitted by another node. This node's tx failed", + }, + { + name: "Returns success if report is on-chain but tx is failed", + initialTransmissionState: writetarget.TransmissionState{Status: writetarget.TransmissionStateNotAttempted}, + txState: commontypes.Failed, + simulateTxError: false, + expectError: false, + requiredLogMessage: "confirmed - transmission state visible but submitted by another node. This node's tx failed", + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + lggr, observed := logger.TestObserved(t, zapcore.DebugLevel) + emitter := monmocks.NewProtoEmitter(t) + strategy := wtmocks.NewTargetStrategy(t) + + mockTransmissionState(tc, strategy) + mockBeholderMessages(tc, emitter) + mockTransmit(tc, strategy, emitter) + + chainSvc := wtmocks.NewChainService(t) + chainSvc.EXPECT().LatestHead(mock.Anything). + Return(commontypes.Head{Height: "100"}, nil) + + target, req := setupWriteTarget(t, lggr, strategy, chainSvc, tc.productSpecificProcessor, emitter) + + resp, err := target.Execute(t.Context(), req) + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errorContains) + } else { + require.NoError(t, err) + assert.NotNil(t, resp) + } + + if tc.requiredLogMessage != "" { + tests.RequireLogMessage(t, observed, tc.requiredLogMessage) + // return len(observed.FilterMessage("no matching processor for MetaCapabilityProcessor=test").All()) > 0 + } + }) + } + + // additional edge cases + t.Run("missing signed report", func(t *testing.T) { + strategy := wtmocks.NewTargetStrategy(t) + chainSvc := wtmocks.NewChainService(t) + emitter := monmocks.NewProtoEmitter(t) + emitter.EXPECT().EmitWithLog(mock.Anything, mock.Anything, mock.Anything).Return(nil) + + target, _ := setupWriteTarget(t, logger.Test(t), strategy, chainSvc, false, emitter) + + inputs, _ := values.NewMap(map[string]any{}) + config, _ := values.NewMap(map[string]any{"address": "x", "processor": "y"}) + req := capabilities.CapabilityRequest{Inputs: inputs, Config: config} + + _, err := target.Execute(context.Background(), req) + require.Error(t, err) + assert.Contains(t, err.Error(), "input missing required field") + }) + + t.Run("invalid config", func(t *testing.T) { + // override ConfigValidateFn + lggr := logger.Test(t) + emitter := monmocks.NewProtoEmitter(t) + emitter.EXPECT().EmitWithLog(mock.Anything, mock.Anything, mock.Anything).Return(nil) + monClient, _ := writetarget.NewMonitor(writetarget.MonitorOpts{lggr, nil, nil, emitter}) + chainSvc := wtmocks.NewChainService(t) + strategy := wtmocks.NewTargetStrategy(t) + + opts := writetarget.WriteTargetOpts{ + ID: "test@1.0.0", + Config: writetarget.Config{}, + ChainInfo: monitor.ChainInfo{}, + Logger: lggr, + Beholder: monClient, + ChainService: chainSvc, + ConfigValidateFn: func(_ capabilities.CapabilityRequest) (string, error) { return "", errors.New("bad cfg") }, + NodeAddress: "x", + ForwarderAddress: "y", + TargetStrategy: strategy, + } + target := writetarget.NewWriteTarget(opts) + + inputs, _ := values.NewMap(map[string]any{"signed_report": map[string]any{"id": []byte{0}, "report": []byte{0}, "context": []byte{}, "signatures": [][]byte{}}}) + config, _ := values.NewMap(map[string]any{"address": "x", "processor": "y"}) + req := capabilities.CapabilityRequest{Inputs: inputs, Config: config} + + _, err := target.Execute(context.Background(), req) + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to validate config") + }) +} + +func mockTransmissionState(tc testCase, strategy *wtmocks.TargetStrategy) { + // initial query for transmission state + strategy.EXPECT().QueryTransmissionState(mock.Anything, mock.Anything, mock.Anything). + Return(&tc.initialTransmissionState, nil).Once() + + // subsequent queries for transaction state (if applicable) + if tc.expectError { + strategy.EXPECT().QueryTransmissionState(mock.Anything, mock.Anything, mock.Anything). + Return(&writetarget.TransmissionState{Status: writetarget.TransmissionStateNotAttempted}, nil).Maybe() + } else { + strategy.EXPECT().QueryTransmissionState(mock.Anything, mock.Anything, mock.Anything). + Return(&writetarget.TransmissionState{Status: writetarget.TransmissionStateSucceeded}, nil).Maybe() + } +} + +func mockBeholderMessages(tc testCase, emitter *monmocks.ProtoEmitter) { + // Ensure the correct beholder messages are emitted for each case + emitter.EXPECT().EmitWithLog(mock.Anything, mock.AnythingOfType("*writetarget.WriteInitiated"), mock.Anything).Return(nil).Once() + if tc.expectError { + emitter.EXPECT().EmitWithLog(mock.Anything, mock.AnythingOfType("*writetarget.WriteError"), mock.Anything).Return(nil).Once() + } else { + emitter.EXPECT().EmitWithLog(mock.Anything, mock.AnythingOfType("*writetarget.WriteConfirmed"), mock.Anything).Return(nil).Once() + emitter.EXPECT().EmitWithLog(mock.Anything, mock.AnythingOfType("*forwarder.ReportProcessed"), mock.Anything).Return(nil).Once() + } +} + +func mockTransmit(tc testCase, strategy *wtmocks.TargetStrategy, emitter *monmocks.ProtoEmitter) { + if tc.txState != commontypes.Unknown { + ex := strategy.EXPECT().TransmitReport(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) + if tc.simulateTxError { + ex.Return("", errors.New("transmit fail")) + } else { + ex.Return("tx123", nil) + emitter.EXPECT().EmitWithLog(mock.Anything, mock.AnythingOfType("*writetarget.WriteSent"), mock.Anything).Return(nil).Once() + strategy.EXPECT().GetTransactionStatus(mock.Anything, mock.Anything).Return(tc.txState, nil) + } + } +} + +func TestNewWriteTargetID(t *testing.T) { + tests := []struct { + name string + chainFamilyName string + networkName string + chainID string + version string + expected string + expectError bool + }{ + { + name: "Valid input with network name", + chainFamilyName: "aptos", + networkName: "mainnet", + chainID: "1", + version: "1.0.0", + expected: "write_aptos-mainnet@1.0.0", + expectError: false, + }, + { + name: "Valid input without network name", + chainFamilyName: "aptos", + networkName: "", + chainID: "1", + version: "1.0.0", + expected: "write_aptos-1@1.0.0", + expectError: false, + }, + { + name: "Valid input with empty chainFamilyName", + chainFamilyName: "", + networkName: "ethereum-mainnet", + chainID: "1", + version: "1.0.0", + expected: "write_ethereum-mainnet@1.0.0", + expectError: false, + }, + { + name: "Invalid input with empty version", + chainFamilyName: "aptos", + networkName: "mainnet", + chainID: "1", + version: "", + expected: "", + expectError: true, + }, + { + name: "Invalid input with empty networkName and chainID", + chainFamilyName: "aptos", + networkName: "", + chainID: "", + version: "2.0.0", + expected: "", + expectError: true, + }, + { + name: "Valid input with unknown network name", + chainFamilyName: "aptos", + networkName: "unknown", + chainID: "1", + version: "2.0.1", + expected: "write_aptos-1@2.0.1", + expectError: false, + }, + { + name: "Valid input with network name (testnet)", + chainFamilyName: "aptos", + networkName: "testnet", + chainID: "2", + version: "1.0.3", + expected: "write_aptos-testnet@1.0.3", + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := writetarget.NewWriteTargetID(tt.chainFamilyName, tt.networkName, tt.chainID, tt.version) + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expected, result) + } + }) + } +}