├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── bigbigendian └── serialize.go ├── commontypes ├── logger.go └── types.go ├── contract ├── AccessControlledOffchainAggregator.sol ├── AccessControllerInterface.sol ├── AggregatorInterface.sol ├── AggregatorV2V3Interface.sol ├── AggregatorV3Interface.sol ├── AggregatorValidatorInterface.sol ├── LinkTokenInterface.sol ├── OffchainAggregator.sol ├── OffchainAggregatorBilling.sol ├── Owned.sol ├── SimpleReadAccessController.sol ├── SimpleWriteAccessController.sol └── TypeAndVersionInterface.sol ├── contract2 ├── AccessControlledOCR2Aggregator.sol ├── ConfirmedOwner.sol ├── ConfirmedOwnerWithProposal.sol ├── OCR2Abstract.sol ├── OCR2Aggregator.sol ├── OCRConfigurationStoreEVMSimple.sol ├── OwnerIsCreator.sol ├── SimpleReadAccessController.sol ├── SimpleWriteAccessController.sol ├── interfaces │ ├── AccessControllerInterface.sol │ ├── AggregatorInterface.sol │ ├── AggregatorV2V3Interface.sol │ ├── AggregatorV3Interface.sol │ ├── AggregatorValidatorInterface.sol │ ├── LinkTokenInterface.sol │ ├── OwnableInterface.sol │ └── TypeAndVersionInterface.sol └── lib │ └── ConfigDigestUtilEVMSimple.sol ├── contract3 ├── OCR3AttestationVerifierBase.sol ├── OCR3AttestationVerifierErrors.sol ├── OCR3BLSAttestationVerifier.sol ├── OCR3BLSAttestationVerifierLib.sol ├── OCR3DynamicallyDispatchedAttestationVerifier.sol ├── OCR3DynamicallyDispatchedBLSAttestationVerifierLib.sol ├── OCR3DynamicallyDispatchedECDSAAttestationVerifierLib.sol ├── OCR3ECDSAAttestationVerifier.sol ├── OCR3ECDSAAttestationVerifierLib.sol └── dev │ ├── DemoBLSAttestationVerifier.sol │ ├── DemoDynamicallyDispatchedAttestationVerifier.sol │ └── DemoECDSAAttestationVerifier.sol ├── gethwrappers ├── accesscontrolledoffchainaggregator │ └── accesscontrolledoffchainaggregator.go ├── accesscontroltesthelper │ └── accesscontroltesthelper.go ├── exposedoffchainaggregator │ └── exposedoffchainaggregator.go ├── link_token_interface │ └── link_token_interface.go ├── offchainaggregator │ └── offchainaggregator.go ├── testoffchainaggregator │ └── testoffchainaggregator.go ├── testvalidator │ └── testvalidator.go └── tools.go ├── gethwrappers2 ├── accesscontrolledocr2aggregator │ └── accesscontrolledocr2aggregator.go ├── accesscontroltesthelper │ └── accesscontroltesthelper.go ├── exposedocr2aggregator │ └── exposedocr2aggregator.go ├── link_token_interface │ └── link_token_interface.go ├── ocr2abstract │ └── ocr2abstract.go ├── ocr2aggregator │ └── ocr2aggregator.go ├── ocr2titlerequest │ └── ocr2titlerequest.go ├── ocrconfigurationstoreevmsimple │ └── ocrconfigurationstoreevmsimple.go ├── testocr2aggregator │ └── testocr2aggregator.go ├── testvalidator │ └── testvalidator.go └── tools.go ├── gethwrappers3 ├── demoblsattestationverifier │ └── demoblsattestationverifier.go ├── demodynamicallydispatchedattestationverifier │ └── demodynamicallydispatchedattestationverifier.go ├── demoecdsaattestationverifier │ └── demoecdsaattestationverifier.go ├── ocr3dynamicallydispatchedblsattestationverifierlib │ └── ocr3dynamicallydispatchedblsattestationverifierlib.go └── ocr3dynamicallydispatchedecdsaattestationverifierlib │ └── ocr3dynamicallydispatchedecdsaattestationverifierlib.go ├── go.mod ├── go.sum ├── internal ├── byzquorum │ └── byzquorum.go ├── configdigesthelper │ └── convert.go ├── loghelper │ ├── close_log_error.go │ ├── if_not_stopped.go │ ├── logger_with_context.go │ └── taper.go ├── metricshelper │ └── registerer.go ├── minigobberish │ └── gobberish.go ├── peerkeyringhelper │ ├── ed25519_sanity_check.go │ └── peer_keyring_with_private_key.go └── util │ └── generic.go ├── networking ├── bootstrapper_v2.go ├── limits.go ├── ocr1_peer.go ├── ocr2_peer.go ├── ocr_endpoint_v2.go ├── peer_group.go ├── peer_v2.go ├── ragedisco │ ├── address.go │ ├── announcement.go │ ├── autodetect │ │ └── autodetect.go │ ├── connectivity.go │ ├── discovery_protocol.go │ ├── group.go │ ├── helpers.go │ ├── metrics.go │ ├── ragep2p_discoverer.go │ └── serialization │ │ └── peer_discovery_announcement.pb.go ├── rageping │ ├── metrics.go │ ├── service.go │ └── types.go └── types │ └── types.go ├── offchainreporting ├── bootstrap_node.go ├── confighelper │ └── confighelper.go ├── doc.go ├── internal │ ├── config │ │ ├── abiencode.go │ │ ├── config_digest.go │ │ ├── encode.go │ │ ├── public_config.go │ │ ├── shared_config.go │ │ ├── shared_secret.go │ │ └── shared_secret_encrypt_xxx.go │ ├── managed │ │ ├── collect_garbage.go │ │ ├── config_overrider.go │ │ ├── doc.go │ │ ├── forward_telemetry.go │ │ ├── load_from_database.go │ │ ├── managed_bootstrap_node.go │ │ ├── managed_oracle.go │ │ └── track_config.go │ ├── protocol │ │ ├── attested_report.go │ │ ├── common.go │ │ ├── heap.go │ │ ├── message.go │ │ ├── messagebuffer.go │ │ ├── network.go │ │ ├── observation.go │ │ ├── observation │ │ │ └── observation.go │ │ ├── oracle.go │ │ ├── pacemaker.go │ │ ├── persist │ │ │ └── persist.go │ │ ├── report_context.go │ │ ├── report_generation.go │ │ ├── report_generation_follower.go │ │ ├── report_generation_leader.go │ │ ├── telemetry.go │ │ ├── test_helpers.go │ │ └── transmission.go │ ├── serialization │ │ ├── protobuf │ │ │ ├── cl_offchainreporting_messages.pb.go │ │ │ └── cl_offchainreporting_telemetry.pb.go │ │ ├── serialization.go │ │ └── telemetry.go │ ├── shim │ │ ├── endpoint.go │ │ └── telemetry_sender.go │ ├── signature │ │ ├── off_chain_signature.go │ │ └── on_chain_signature.go │ └── test │ │ └── doc.go ├── oracle.go ├── types │ ├── constants.go │ ├── db.go │ ├── local_config.go │ └── types.go └── validate_local_config.go ├── offchainreporting2 ├── chains │ └── evmutil │ │ └── alias.go ├── confighelper │ └── alias.go ├── reportingplugin │ ├── median │ │ ├── epochround.go │ │ ├── evmreportcodec │ │ │ └── reportcodec.go │ │ ├── median.go │ │ ├── offchainreporting2_median_config.pb.go │ │ ├── offchainreporting2_median_observation.pb.go │ │ └── value.go │ └── titlerequest │ │ └── titlerequest.go └── types │ └── alias.go ├── offchainreporting2plus ├── bootstrapper.go ├── chains │ └── evmutil │ │ ├── config_digest.go │ │ ├── evm.go │ │ └── offchain_config_digester.go ├── confighelper │ └── confighelper.go ├── doc.go ├── internal │ ├── config │ │ ├── ethcontractconfig │ │ │ └── ethereum_set_config_args.go │ │ ├── netconfig │ │ │ └── netconfig.go │ │ ├── ocr2config │ │ │ ├── metrics.go │ │ │ ├── offchainreporting2_offchain_config.pb.go │ │ │ ├── public_config.go │ │ │ ├── serialize.go │ │ │ └── shared_config.go │ │ ├── ocr3config │ │ │ ├── metrics.go │ │ │ ├── offchainreporting3_offchain_config.pb.go │ │ │ ├── public_config.go │ │ │ ├── serialize.go │ │ │ └── shared_config.go │ │ ├── offchain_config_versions.go │ │ ├── oracle_identity.go │ │ ├── shared_secret.go │ │ └── shared_secret_encrypt.go │ ├── managed │ │ ├── collect_garbage.go │ │ ├── config_digest.go │ │ ├── doc.go │ │ ├── forward_telemetry.go │ │ ├── limits │ │ │ ├── ocr2_limits.go │ │ │ ├── ocr3_limits.go │ │ │ └── util.go │ │ ├── load_from_database.go │ │ ├── managed_bootstrapper.go │ │ ├── managed_mercury_oracle.go │ │ ├── managed_ocr2_oracle.go │ │ ├── managed_ocr3_oracle.go │ │ ├── run_with_contract_config.go │ │ └── track_config.go │ ├── mercuryshim │ │ └── mercuryshims.go │ ├── ocr2 │ │ ├── protocol │ │ │ ├── attested_report.go │ │ │ ├── attested_report_test_equal.go │ │ │ ├── common.go │ │ │ ├── heap.go │ │ │ ├── message.go │ │ │ ├── message_test_equal.go │ │ │ ├── messagebuffer.go │ │ │ ├── metrics.go │ │ │ ├── network.go │ │ │ ├── observation.go │ │ │ ├── oracle.go │ │ │ ├── pacemaker.go │ │ │ ├── persist │ │ │ │ ├── persist_pacemaker.go │ │ │ │ └── persist_transmission.go │ │ │ ├── report_finalization.go │ │ │ ├── report_generation.go │ │ │ ├── report_generation_follower.go │ │ │ ├── report_generation_leader.go │ │ │ ├── telemetry.go │ │ │ ├── test_helpers.go │ │ │ └── transmission.go │ │ └── serialization │ │ │ ├── offchainreporting2_messages.pb.go │ │ │ ├── offchainreporting2_telemetry.pb.go │ │ │ ├── serialization.go │ │ │ └── telemetry.go │ ├── ocr3 │ │ ├── minheap │ │ │ └── minheap.go │ │ ├── protocol │ │ │ ├── attested_report.go │ │ │ ├── common.go │ │ │ ├── db.go │ │ │ ├── message.go │ │ │ ├── messagebuffer.go │ │ │ ├── metrics.go │ │ │ ├── network.go │ │ │ ├── oracle.go │ │ │ ├── outcome_generation.go │ │ │ ├── outcome_generation_follower.go │ │ │ ├── outcome_generation_leader.go │ │ │ ├── pacemaker.go │ │ │ ├── pool │ │ │ │ └── pool.go │ │ │ ├── report_attestation.go │ │ │ ├── ringbuffer │ │ │ │ └── ringbuffer.go │ │ │ ├── signed_data.go │ │ │ ├── telemetry.go │ │ │ └── transmission.go │ │ ├── scheduler │ │ │ └── scheduler.go │ │ └── serialization │ │ │ ├── offchainreporting3_db.pb.go │ │ │ ├── offchainreporting3_messages.pb.go │ │ │ ├── offchainreporting3_telemetry.pb.go │ │ │ ├── serialization.go │ │ │ └── telemetry.go │ └── shim │ │ ├── metrics.go │ │ ├── ocr2_serializing_endpoint.go │ │ ├── ocr2_telemetry_sender.go │ │ ├── ocr3_database.go │ │ ├── ocr3_reporting_plugin.go │ │ ├── ocr3_serializing_endpoint.go │ │ ├── ocr3_telemetry_sender.go │ │ └── reporting_plugin.go ├── ocr3confighelper │ └── ocr3confighelper.go ├── ocr3types │ ├── db.go │ ├── mercury_plugin.go │ ├── plugin.go │ └── types.go ├── oracle.go ├── types │ ├── config_digest.go │ ├── constants.go │ ├── db.go │ ├── local_config.go │ └── types.go └── validate_local_config.go ├── permutation └── permutation.go ├── quorumhelper └── quorumhelper.go ├── ragep2p ├── conn_rate_limiter.go ├── demuxer.go ├── doc.go ├── frame.go ├── internal │ ├── knock │ │ └── knock.go │ ├── msgbuf │ │ └── ringbuffer.go │ ├── mtls │ │ ├── common.go │ │ └── crypto_signer_adapter.go │ ├── ratelimit │ │ └── ratelimit.go │ └── ratelimitedconn │ │ └── rate_limited_conn.go ├── loggers │ └── logrus.go ├── metrics.go ├── ragep2p.go ├── tls_config.go └── types │ └── types.go └── subprocesses └── subprocesses.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | pull_request: 8 | branches: [ "master" ] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version-file: 'go.mod' 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/macos,linux,emacs,vim,go,vscode 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,linux,emacs,vim,go,vscode 4 | 5 | ### Emacs ### 6 | # -*- mode: gitignore; -*- 7 | *~ 8 | \#*\# 9 | /.emacs.desktop 10 | /.emacs.desktop.lock 11 | *.elc 12 | auto-save-list 13 | tramp 14 | .\#* 15 | 16 | # Org-mode 17 | .org-id-locations 18 | *_archive 19 | 20 | # flymake-mode 21 | *_flymake.* 22 | 23 | # eshell files 24 | /eshell/history 25 | /eshell/lastdir 26 | 27 | # elpa packages 28 | /elpa/ 29 | 30 | # reftex files 31 | *.rel 32 | 33 | # AUCTeX auto folder 34 | /auto/ 35 | 36 | # cask packages 37 | .cask/ 38 | dist/ 39 | 40 | # Flycheck 41 | flycheck_*.el 42 | 43 | # server auth directory 44 | /server/ 45 | 46 | # projectiles files 47 | .projectile 48 | 49 | # directory configuration 50 | .dir-locals.el 51 | 52 | # network security 53 | /network-security.data 54 | 55 | 56 | ### Go ### 57 | # Binaries for programs and plugins 58 | *.exe 59 | *.exe~ 60 | *.dll 61 | *.so 62 | *.dylib 63 | 64 | # Test binary, built with `go test -c` 65 | *.test 66 | 67 | # Output of the go coverage tool, specifically when used with LiteIDE 68 | *.out 69 | 70 | # Dependency directories (remove the comment below to include it) 71 | # vendor/ 72 | 73 | ### Go Patch ### 74 | /vendor/ 75 | /Godeps/ 76 | 77 | ### Linux ### 78 | 79 | # temporary files which can be created if a process still has a handle open of a deleted file 80 | .fuse_hidden* 81 | 82 | # KDE directory preferences 83 | .directory 84 | 85 | # Linux trash folder which might appear on any partition or disk 86 | .Trash-* 87 | 88 | # .nfs files are created when an open file is removed but is still being accessed 89 | .nfs* 90 | 91 | ### macOS ### 92 | # General 93 | .DS_Store 94 | .AppleDouble 95 | .LSOverride 96 | 97 | # Icon must end with two \r 98 | Icon 99 | 100 | # Thumbnails 101 | ._* 102 | 103 | # Files that might appear in the root of a volume 104 | .DocumentRevisions-V100 105 | .fseventsd 106 | .Spotlight-V100 107 | .TemporaryItems 108 | .Trashes 109 | .VolumeIcon.icns 110 | .com.apple.timemachine.donotpresent 111 | 112 | # Directories potentially created on remote AFP share 113 | .AppleDB 114 | .AppleDesktop 115 | Network Trash Folder 116 | Temporary Items 117 | .apdisk 118 | 119 | ### Vim ### 120 | # Swap 121 | [._]*.s[a-v][a-z] 122 | !*.svg # comment out if you don't need vector files 123 | [._]*.sw[a-p] 124 | [._]s[a-rt-v][a-z] 125 | [._]ss[a-gi-z] 126 | [._]sw[a-p] 127 | 128 | # Session 129 | Session.vim 130 | Sessionx.vim 131 | 132 | # Temporary 133 | .netrwhist 134 | # Auto-generated tag files 135 | tags 136 | # Persistent undo 137 | [._]*.un~ 138 | 139 | ### vscode ### 140 | .vscode/* 141 | !.vscode/settings.json 142 | !.vscode/tasks.json 143 | !.vscode/launch.json 144 | !.vscode/extensions.json 145 | *.code-workspace 146 | 147 | # End of https://www.toptal.com/developers/gitignore/api/macos,linux,emacs,vim,go,vscode 148 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 SmartContract ChainLink, Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libocr 2 | 3 | libocr consists of a Go library and a set of Solidity smart contracts that implement the *Chainlink Offchain Reporting Protocol*, a [Byzantine fault tolerant](https://en.wikipedia.org/wiki/Byzantine_fault) protocol that allows a set of oracles to generate *offchain* an aggregate report of the oracles' observations of some underlying data source. This report is then transmitted to an onchain contract in a single transaction. 4 | 5 | You may also be interested in [libocr's integration into the actual Chainlink node](https://github.com/smartcontractkit/chainlink/tree/develop/core/services/offchainreporting). 6 | 7 | 8 | ## Protocol Description 9 | 10 | Protocol execution mostly happens offchain over a peer to peer network between Chainlink nodes. The nodes regularly elect a new leader node who drives the rest of the protocol. The protocol is designed to choose each leader fairly and quickly rotate away from leaders that aren’t making progress towards timely onchain reports. 11 | 12 | The leader regularly requests followers to provide freshly signed observations and aggregates them into a report. It then sends the aggregate report back to the followers and asks them to attest to the report's validity by signing it. If a quorum of followers approves the report, the leader assembles a final report with the quorum's signatures and broadcasts it to all followers. 13 | 14 | The nodes then attempt to transmit the final report to the smart contract according to a randomized schedule. Finally, the smart contract verifies that a quorum of nodes signed the report and exposes the median value to consumers. 15 | 16 | 17 | ## Organization 18 | ``` 19 | . 20 | ├── contract: Ethereum smart contracts 21 | ├── gethwrappers: go-ethereum bindings for the OCR1 contracts, generated with abigen 22 | ├── gethwrappers2: go-ethereum bindings for the OCR2 contracts, generated with abigen 23 | ├── networking: p2p networking layer 24 | ├── offchainreporting: offchain reporting protocol version 1 25 | ├── offchainreporting2: offchain reporting protocol version 2 specific packages, not much here 26 | ├── offchainreporting2plus: offchain reporting protocol version 2 and beyond 27 | ├── permutation: helper package for generating permutations 28 | └── subprocesses: helper package for managing go routines 29 | ``` 30 | -------------------------------------------------------------------------------- /bigbigendian/serialize.go: -------------------------------------------------------------------------------- 1 | // Serializes and deserializes big ints using two's complement big endian representation 2 | package bigbigendian 3 | 4 | import ( 5 | "fmt" 6 | "math/big" 7 | ) 8 | 9 | // The maximum size (in bytes) of serialized representation we support 10 | const MaxSize = 128 11 | 12 | // Serializes a signed big.Int into a byte slice with size bytes. Does not mutate its inputs. Does not panic. 13 | func SerializeSigned(size int, i *big.Int) ([]byte, error) { 14 | if i == nil { 15 | return nil, fmt.Errorf("i is nil") 16 | } 17 | 18 | if !(0 < size && size <= MaxSize) { 19 | return nil, fmt.Errorf("size is %v, but must be between 1 and %v", size, MaxSize) 20 | } 21 | 22 | bitSize := size * 8 23 | negative := i.Sign() < 0 24 | 25 | b := make([]byte, size) 26 | if negative { 27 | // To find the two's complement we subtract one from the absolute value, then invert 28 | 29 | // If input is valid, max abs(i) here: 2**(bitSize - 1) 30 | 31 | tmp := big.NewInt(1) 32 | tmp.Add(i, tmp) // i is negative, so to subtract from its absolute value we need to add 33 | 34 | // If input is valid, max abs(tmp) here: 2**(bitSize - 1)-1 = 2**(bitSize - 2) + ... + 2**0 35 | 36 | if bitSize <= tmp.BitLen() { 37 | return nil, fmt.Errorf("i doesn't fit into a %v-byte two's complement", size) 38 | } 39 | tmp.FillBytes(b) // encodes abs(tmp) into b 40 | for i := range b { 41 | b[i] ^= 0xff 42 | } 43 | } else { 44 | if bitSize <= i.BitLen() { 45 | return nil, fmt.Errorf("i doesn't fit into a %v-byte two's complement", size) 46 | } 47 | i.FillBytes(b) 48 | } 49 | return b, nil 50 | } 51 | 52 | // Deserializes a byte slice with size bytes into a signed big.Int. Does not mutate its inputs. Does not panic. 53 | func DeserializeSigned(size int, b []byte) (*big.Int, error) { 54 | if !(0 < size && size <= MaxSize) { 55 | return nil, fmt.Errorf("size is %v, but must be between 1 and %v", size, MaxSize) 56 | } 57 | if len(b) != size { 58 | return &big.Int{}, fmt.Errorf("expected b to have length %v, but got length %v", size, len(b)) 59 | } 60 | bitSize := size * 8 61 | val := (&big.Int{}).SetBytes(b) 62 | negative := b[0]&0x80 != 0 63 | if negative { 64 | // In two's complement representation, the msb has a negative sign, e.g. 65 | // "1011" represents -(2**3) + 2**1 + 2**0. 66 | // However, SetBytes considered the msb to have a positive sign. 67 | // We thus compute val - 2**(bitSize-1) - 2**(bitSize-1) = val - 2**bitSize. 68 | powerOfTwo := (&big.Int{}).SetInt64(1) 69 | powerOfTwo.Lsh(powerOfTwo, uint(bitSize)) 70 | val.Sub(val, powerOfTwo) 71 | } 72 | return val, nil 73 | } 74 | -------------------------------------------------------------------------------- /commontypes/logger.go: -------------------------------------------------------------------------------- 1 | package commontypes 2 | 3 | // Loggers logs things using a structured-logging approach. 4 | // All its functions should be thread-safe. 5 | // It is acceptable to pass a nil LogFields to all of its functions. 6 | type Logger interface { 7 | Trace(msg string, fields LogFields) 8 | Debug(msg string, fields LogFields) 9 | Info(msg string, fields LogFields) 10 | Warn(msg string, fields LogFields) 11 | Error(msg string, fields LogFields) 12 | Critical(msg string, fields LogFields) 13 | } 14 | 15 | type LogFields map[string]interface{} 16 | -------------------------------------------------------------------------------- /contract/AccessControllerInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.0; 3 | 4 | interface AccessControllerInterface { 5 | function hasAccess(address user, bytes calldata data) external view returns (bool); 6 | } 7 | -------------------------------------------------------------------------------- /contract/AggregatorInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.0; 3 | 4 | interface AggregatorInterface { 5 | function latestAnswer() external view returns (int256); 6 | function latestTimestamp() external view returns (uint256); 7 | function latestRound() external view returns (uint256); 8 | function getAnswer(uint256 roundId) external view returns (int256); 9 | function getTimestamp(uint256 roundId) external view returns (uint256); 10 | 11 | event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt); 12 | event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt); 13 | } 14 | -------------------------------------------------------------------------------- /contract/AggregatorV2V3Interface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.0; 3 | 4 | import "./AggregatorInterface.sol"; 5 | import "./AggregatorV3Interface.sol"; 6 | 7 | interface AggregatorV2V3Interface is AggregatorInterface, AggregatorV3Interface 8 | { 9 | } -------------------------------------------------------------------------------- /contract/AggregatorV3Interface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.0; 3 | 4 | interface AggregatorV3Interface { 5 | 6 | function decimals() external view returns (uint8); 7 | function description() external view returns (string memory); 8 | function version() external view returns (uint256); 9 | 10 | function getRoundData(uint80 _roundId) 11 | external 12 | view 13 | returns ( 14 | uint80 roundId, 15 | int256 answer, 16 | uint256 startedAt, 17 | uint256 updatedAt, 18 | uint80 answeredInRound 19 | ); 20 | function latestRoundData() 21 | external 22 | view 23 | returns ( 24 | uint80 roundId, 25 | int256 answer, 26 | uint256 startedAt, 27 | uint256 updatedAt, 28 | uint80 answeredInRound 29 | ); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /contract/AggregatorValidatorInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.0; 3 | 4 | interface AggregatorValidatorInterface { 5 | function validate( 6 | uint256 previousRoundId, 7 | int256 previousAnswer, 8 | uint256 currentRoundId, 9 | int256 currentAnswer 10 | ) external returns (bool); 11 | } -------------------------------------------------------------------------------- /contract/LinkTokenInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.0; 3 | 4 | interface LinkTokenInterface { 5 | function allowance(address owner, address spender) external view returns (uint256 remaining); 6 | function approve(address spender, uint256 value) external returns (bool success); 7 | function balanceOf(address owner) external view returns (uint256 balance); 8 | function decimals() external view returns (uint8 decimalPlaces); 9 | function decreaseApproval(address spender, uint256 addedValue) external returns (bool success); 10 | function increaseApproval(address spender, uint256 subtractedValue) external; 11 | function name() external view returns (string memory tokenName); 12 | function symbol() external view returns (string memory tokenSymbol); 13 | function totalSupply() external view returns (uint256 totalTokensIssued); 14 | function transfer(address to, uint256 value) external returns (bool success); 15 | function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool success); 16 | function transferFrom(address from, address to, uint256 value) external returns (bool success); 17 | } 18 | -------------------------------------------------------------------------------- /contract/Owned.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.7.6; 3 | 4 | /** 5 | * @title The Owned contract 6 | * @notice A contract with helpers for basic contract ownership. 7 | */ 8 | contract Owned { 9 | 10 | address payable public owner; 11 | address private pendingOwner; 12 | 13 | event OwnershipTransferRequested( 14 | address indexed from, 15 | address indexed to 16 | ); 17 | event OwnershipTransferred( 18 | address indexed from, 19 | address indexed to 20 | ); 21 | 22 | constructor() { 23 | owner = msg.sender; 24 | } 25 | 26 | /** 27 | * @dev Allows an owner to begin transferring ownership to a new address, 28 | * pending. 29 | */ 30 | function transferOwnership(address _to) 31 | external 32 | onlyOwner() 33 | { 34 | pendingOwner = _to; 35 | 36 | emit OwnershipTransferRequested(owner, _to); 37 | } 38 | 39 | /** 40 | * @dev Allows an ownership transfer to be completed by the recipient. 41 | */ 42 | function acceptOwnership() 43 | external 44 | { 45 | require(msg.sender == pendingOwner, "Must be proposed owner"); 46 | 47 | address oldOwner = owner; 48 | owner = msg.sender; 49 | pendingOwner = address(0); 50 | 51 | emit OwnershipTransferred(oldOwner, msg.sender); 52 | } 53 | 54 | /** 55 | * @dev Reverts if called by anyone other than the contract owner. 56 | */ 57 | modifier onlyOwner() { 58 | require(msg.sender == owner, "Only callable by owner"); 59 | _; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /contract/SimpleReadAccessController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.7.6; 3 | 4 | import "./SimpleWriteAccessController.sol"; 5 | 6 | /** 7 | * @title SimpleReadAccessController 8 | * @notice Gives access to: 9 | * - any externally owned account (note that offchain actors can always read 10 | * any contract storage regardless of onchain access control measures, so this 11 | * does not weaken the access control while improving usability) 12 | * - accounts explicitly added to an access list 13 | * @dev SimpleReadAccessController is not suitable for access controlling writes 14 | * since it grants any externally owned account access! See 15 | * SimpleWriteAccessController for that. 16 | */ 17 | contract SimpleReadAccessController is SimpleWriteAccessController { 18 | 19 | /** 20 | * @notice Returns the access of an address 21 | * @param _user The address to query 22 | */ 23 | function hasAccess( 24 | address _user, 25 | bytes memory _calldata 26 | ) 27 | public 28 | view 29 | virtual 30 | override 31 | returns (bool) 32 | { 33 | return super.hasAccess(_user, _calldata) || _user == tx.origin; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /contract/SimpleWriteAccessController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.7.6; 3 | 4 | import "./Owned.sol"; 5 | import "./AccessControllerInterface.sol"; 6 | 7 | /** 8 | * @title SimpleWriteAccessController 9 | * @notice Gives access to accounts explicitly added to an access list by the 10 | * controller's owner. 11 | * @dev does not make any special permissions for externally, see 12 | * SimpleReadAccessController for that. 13 | */ 14 | contract SimpleWriteAccessController is AccessControllerInterface, Owned { 15 | 16 | bool public checkEnabled; 17 | mapping(address => bool) internal accessList; 18 | 19 | event AddedAccess(address user); 20 | event RemovedAccess(address user); 21 | event CheckAccessEnabled(); 22 | event CheckAccessDisabled(); 23 | 24 | constructor() 25 | { 26 | checkEnabled = true; 27 | } 28 | 29 | /** 30 | * @notice Returns the access of an address 31 | * @param _user The address to query 32 | */ 33 | function hasAccess( 34 | address _user, 35 | bytes memory 36 | ) 37 | public 38 | view 39 | virtual 40 | override 41 | returns (bool) 42 | { 43 | return accessList[_user] || !checkEnabled; 44 | } 45 | 46 | /** 47 | * @notice Adds an address to the access list 48 | * @param _user The address to add 49 | */ 50 | function addAccess(address _user) external onlyOwner() { 51 | addAccessInternal(_user); 52 | } 53 | 54 | function addAccessInternal(address _user) internal { 55 | if (!accessList[_user]) { 56 | accessList[_user] = true; 57 | emit AddedAccess(_user); 58 | } 59 | } 60 | 61 | /** 62 | * @notice Removes an address from the access list 63 | * @param _user The address to remove 64 | */ 65 | function removeAccess(address _user) 66 | external 67 | onlyOwner() 68 | { 69 | if (accessList[_user]) { 70 | accessList[_user] = false; 71 | 72 | emit RemovedAccess(_user); 73 | } 74 | } 75 | 76 | /** 77 | * @notice makes the access check enforced 78 | */ 79 | function enableAccessCheck() 80 | external 81 | onlyOwner() 82 | { 83 | if (!checkEnabled) { 84 | checkEnabled = true; 85 | 86 | emit CheckAccessEnabled(); 87 | } 88 | } 89 | 90 | /** 91 | * @notice makes the access check unenforced 92 | */ 93 | function disableAccessCheck() 94 | external 95 | onlyOwner() 96 | { 97 | if (checkEnabled) { 98 | checkEnabled = false; 99 | 100 | emit CheckAccessDisabled(); 101 | } 102 | } 103 | 104 | /** 105 | * @dev reverts if the caller does not have access 106 | */ 107 | modifier checkAccess() { 108 | require(hasAccess(msg.sender, msg.data), "No access"); 109 | _; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /contract/TypeAndVersionInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.0; 3 | 4 | abstract contract TypeAndVersionInterface{ 5 | function typeAndVersion() 6 | external 7 | pure 8 | virtual 9 | returns (string memory); 10 | } -------------------------------------------------------------------------------- /contract2/ConfirmedOwner.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "./ConfirmedOwnerWithProposal.sol"; 5 | 6 | /** 7 | * @title The ConfirmedOwner contract 8 | * @notice A contract with helpers for basic contract ownership. 9 | */ 10 | contract ConfirmedOwner is ConfirmedOwnerWithProposal { 11 | 12 | constructor( 13 | address newOwner 14 | ) 15 | ConfirmedOwnerWithProposal( 16 | newOwner, 17 | address(0) 18 | ) 19 | { 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /contract2/ConfirmedOwnerWithProposal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "./interfaces/OwnableInterface.sol"; 5 | 6 | /** 7 | * @title The ConfirmedOwner contract 8 | * @notice A contract with helpers for basic contract ownership. 9 | */ 10 | contract ConfirmedOwnerWithProposal is OwnableInterface { 11 | 12 | address private s_owner; 13 | address private s_pendingOwner; 14 | 15 | event OwnershipTransferRequested( 16 | address indexed from, 17 | address indexed to 18 | ); 19 | event OwnershipTransferred( 20 | address indexed from, 21 | address indexed to 22 | ); 23 | 24 | constructor( 25 | address newOwner, 26 | address pendingOwner 27 | ) { 28 | require(newOwner != address(0), "Cannot set owner to zero"); 29 | 30 | s_owner = newOwner; 31 | if (pendingOwner != address(0)) { 32 | _transferOwnership(pendingOwner); 33 | } 34 | } 35 | 36 | /** 37 | * @notice Allows an owner to begin transferring ownership to a new address, 38 | * pending. 39 | */ 40 | function transferOwnership( 41 | address to 42 | ) 43 | public 44 | override 45 | onlyOwner() 46 | { 47 | _transferOwnership(to); 48 | } 49 | 50 | /** 51 | * @notice Allows an ownership transfer to be completed by the recipient. 52 | */ 53 | function acceptOwnership() 54 | external 55 | override 56 | { 57 | require(msg.sender == s_pendingOwner, "Must be proposed owner"); 58 | 59 | address oldOwner = s_owner; 60 | s_owner = msg.sender; 61 | s_pendingOwner = address(0); 62 | 63 | emit OwnershipTransferred(oldOwner, msg.sender); 64 | } 65 | 66 | /** 67 | * @notice Get the current owner 68 | */ 69 | function owner() 70 | public 71 | view 72 | override 73 | returns ( 74 | address 75 | ) 76 | { 77 | return s_owner; 78 | } 79 | 80 | /** 81 | * @notice validate, transfer ownership, and emit relevant events 82 | */ 83 | function _transferOwnership( 84 | address to 85 | ) 86 | private 87 | { 88 | require(to != msg.sender, "Cannot transfer to self"); 89 | 90 | s_pendingOwner = to; 91 | 92 | emit OwnershipTransferRequested(s_owner, to); 93 | } 94 | 95 | /** 96 | * @notice validate access 97 | */ 98 | function _validateOwnership() 99 | internal 100 | view 101 | { 102 | require(msg.sender == s_owner, "Only callable by owner"); 103 | } 104 | 105 | /** 106 | * @notice Reverts if called by anyone other than the contract owner. 107 | */ 108 | modifier onlyOwner() { 109 | _validateOwnership(); 110 | _; 111 | } 112 | 113 | } -------------------------------------------------------------------------------- /contract2/OCRConfigurationStoreEVMSimple.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity =0.8.19; 3 | 4 | import "./interfaces/TypeAndVersionInterface.sol"; 5 | import "./lib/ConfigDigestUtilEVMSimple.sol"; 6 | import "./OwnerIsCreator.sol"; 7 | import "./OCR2Abstract.sol"; 8 | 9 | /// @title OCRConfigurationStoreEVMSimple 10 | /// @notice This contract stores configurations for protocol versions OCR2 and 11 | /// above in contract storage. It uses the "EVMSimple" config digester. 12 | contract OCRConfigurationStoreEVMSimple is TypeAndVersionInterface { 13 | 14 | struct ConfigurationEVMSimple { 15 | address[] signers; 16 | address[] transmitters; 17 | bytes onchainConfig; 18 | bytes offchainConfig; 19 | address contractAddress; 20 | uint64 offchainConfigVersion; 21 | uint32 configCount; 22 | uint8 f; 23 | } 24 | 25 | /// @notice a list of configurations keyed by their digest 26 | mapping(bytes32 => ConfigurationEVMSimple) internal s_configurations; 27 | 28 | /// @notice emitted when a new configuration is added 29 | event NewConfiguration(bytes32 indexed configDigest); 30 | 31 | /// @notice adds a new configuration to the store 32 | function addConfig(ConfigurationEVMSimple calldata configuration) external returns (bytes32) { 33 | 34 | bytes32 configDigest = ConfigDigestUtilEVMSimple.configDigestFromConfigData( 35 | block.chainid, 36 | configuration.contractAddress, 37 | configuration.configCount, 38 | configuration.signers, 39 | configuration.transmitters, 40 | configuration.f, 41 | configuration.onchainConfig, 42 | configuration.offchainConfigVersion, 43 | configuration.offchainConfig 44 | ); 45 | 46 | s_configurations[configDigest] = configuration; 47 | 48 | emit NewConfiguration(configDigest); 49 | 50 | return configDigest; 51 | } 52 | 53 | /// @notice reads a configuration from the store 54 | function readConfig(bytes32 configDigest) external view returns (ConfigurationEVMSimple memory) { 55 | return s_configurations[configDigest]; 56 | } 57 | 58 | /// @inheritdoc TypeAndVersionInterface 59 | function typeAndVersion() external override pure virtual returns (string memory) 60 | { 61 | return "OCRConfigurationStoreEVMSimple 1.0.0"; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /contract2/OwnerIsCreator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "./ConfirmedOwner.sol"; 5 | 6 | /** 7 | * @title The OwnerIsCreator contract 8 | * @notice A contract with helpers for basic contract ownership. 9 | */ 10 | contract OwnerIsCreator is ConfirmedOwner { 11 | 12 | constructor( 13 | ) 14 | ConfirmedOwner( 15 | msg.sender 16 | ) 17 | { 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /contract2/SimpleReadAccessController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "./SimpleWriteAccessController.sol"; 5 | 6 | /** 7 | * @title SimpleReadAccessController 8 | * @notice Gives access to: 9 | * - any externally owned account (note that offchain actors can always read 10 | * any contract storage regardless of onchain access control measures, so this 11 | * does not weaken the access control while improving usability) 12 | * - accounts explicitly added to an access list 13 | * @dev SimpleReadAccessController is not suitable for access controlling writes 14 | * since it grants any externally owned account access! See 15 | * SimpleWriteAccessController for that. 16 | */ 17 | contract SimpleReadAccessController is SimpleWriteAccessController { 18 | 19 | /** 20 | * @notice Returns the access of an address 21 | * @param _user The address to query 22 | */ 23 | function hasAccess( 24 | address _user, 25 | bytes memory _calldata 26 | ) 27 | public 28 | view 29 | virtual 30 | override 31 | returns (bool) 32 | { 33 | return super.hasAccess(_user, _calldata) || _user == tx.origin; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /contract2/SimpleWriteAccessController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "./OwnerIsCreator.sol"; 5 | import "./interfaces/AccessControllerInterface.sol"; 6 | 7 | /** 8 | * @title SimpleWriteAccessController 9 | * @notice Gives access to accounts explicitly added to an access list by the 10 | * controller's owner. 11 | * @dev does not make any special permissions for externally, see 12 | * SimpleReadAccessController for that. 13 | */ 14 | contract SimpleWriteAccessController is AccessControllerInterface, OwnerIsCreator { 15 | 16 | bool public checkEnabled; 17 | mapping(address => bool) internal accessList; 18 | 19 | event AddedAccess(address user); 20 | event RemovedAccess(address user); 21 | event CheckAccessEnabled(); 22 | event CheckAccessDisabled(); 23 | 24 | constructor() 25 | // TODO 26 | // this is modified from the version in the Chainlink monorepo 27 | // OwnerIsCreator() 28 | { 29 | checkEnabled = true; 30 | } 31 | 32 | /** 33 | * @notice Returns the access of an address 34 | * @param _user The address to query 35 | */ 36 | function hasAccess( 37 | address _user, 38 | bytes memory 39 | ) 40 | public 41 | view 42 | virtual 43 | override 44 | returns (bool) 45 | { 46 | return accessList[_user] || !checkEnabled; 47 | } 48 | 49 | /** 50 | * @notice Adds an address to the access list 51 | * @param _user The address to add 52 | */ 53 | function addAccess(address _user) 54 | external 55 | onlyOwner() 56 | { 57 | if (!accessList[_user]) { 58 | accessList[_user] = true; 59 | 60 | emit AddedAccess(_user); 61 | } 62 | } 63 | 64 | /** 65 | * @notice Removes an address from the access list 66 | * @param _user The address to remove 67 | */ 68 | function removeAccess(address _user) 69 | external 70 | onlyOwner() 71 | { 72 | if (accessList[_user]) { 73 | accessList[_user] = false; 74 | 75 | emit RemovedAccess(_user); 76 | } 77 | } 78 | 79 | /** 80 | * @notice makes the access check enforced 81 | */ 82 | function enableAccessCheck() 83 | external 84 | onlyOwner() 85 | { 86 | if (!checkEnabled) { 87 | checkEnabled = true; 88 | 89 | emit CheckAccessEnabled(); 90 | } 91 | } 92 | 93 | /** 94 | * @notice makes the access check unenforced 95 | */ 96 | function disableAccessCheck() 97 | external 98 | onlyOwner() 99 | { 100 | if (checkEnabled) { 101 | checkEnabled = false; 102 | 103 | emit CheckAccessDisabled(); 104 | } 105 | } 106 | 107 | /** 108 | * @dev reverts if the caller does not have access 109 | */ 110 | modifier checkAccess() { 111 | require(hasAccess(msg.sender, msg.data), "No access"); 112 | _; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /contract2/interfaces/AccessControllerInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface AccessControllerInterface { 5 | function hasAccess(address user, bytes calldata data) external view returns (bool); 6 | } 7 | -------------------------------------------------------------------------------- /contract2/interfaces/AggregatorInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface AggregatorInterface { 5 | function latestAnswer() external view returns (int256); 6 | function latestTimestamp() external view returns (uint256); 7 | function latestRound() external view returns (uint256); 8 | function getAnswer(uint256 roundId) external view returns (int256); 9 | function getTimestamp(uint256 roundId) external view returns (uint256); 10 | 11 | event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt); 12 | event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt); 13 | } 14 | -------------------------------------------------------------------------------- /contract2/interfaces/AggregatorV2V3Interface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "./AggregatorInterface.sol"; 5 | import "./AggregatorV3Interface.sol"; 6 | 7 | interface AggregatorV2V3Interface is AggregatorInterface, AggregatorV3Interface 8 | { 9 | } -------------------------------------------------------------------------------- /contract2/interfaces/AggregatorV3Interface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface AggregatorV3Interface { 5 | 6 | function decimals() external view returns (uint8); 7 | function description() external view returns (string memory); 8 | function version() external view returns (uint256); 9 | 10 | function getRoundData(uint80 _roundId) 11 | external 12 | view 13 | returns ( 14 | uint80 roundId, 15 | int256 answer, 16 | uint256 startedAt, 17 | uint256 updatedAt, 18 | uint80 answeredInRound 19 | ); 20 | function latestRoundData() 21 | external 22 | view 23 | returns ( 24 | uint80 roundId, 25 | int256 answer, 26 | uint256 startedAt, 27 | uint256 updatedAt, 28 | uint80 answeredInRound 29 | ); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /contract2/interfaces/AggregatorValidatorInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface AggregatorValidatorInterface { 5 | function validate( 6 | uint256 previousRoundId, 7 | int256 previousAnswer, 8 | uint256 currentRoundId, 9 | int256 currentAnswer 10 | ) external returns (bool); 11 | } -------------------------------------------------------------------------------- /contract2/interfaces/LinkTokenInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface LinkTokenInterface { 5 | function allowance(address owner, address spender) external view returns (uint256 remaining); 6 | function approve(address spender, uint256 value) external returns (bool success); 7 | function balanceOf(address owner) external view returns (uint256 balance); 8 | function decimals() external view returns (uint8 decimalPlaces); 9 | function decreaseApproval(address spender, uint256 addedValue) external returns (bool success); 10 | function increaseApproval(address spender, uint256 subtractedValue) external; 11 | function name() external view returns (string memory tokenName); 12 | function symbol() external view returns (string memory tokenSymbol); 13 | function totalSupply() external view returns (uint256 totalTokensIssued); 14 | function transfer(address to, uint256 value) external returns (bool success); 15 | function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool success); 16 | function transferFrom(address from, address to, uint256 value) external returns (bool success); 17 | } 18 | -------------------------------------------------------------------------------- /contract2/interfaces/OwnableInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface OwnableInterface { 5 | function owner() 6 | external 7 | returns ( 8 | address 9 | ); 10 | 11 | function transferOwnership( 12 | address recipient 13 | ) 14 | external; 15 | 16 | function acceptOwnership() 17 | external; 18 | } 19 | -------------------------------------------------------------------------------- /contract2/interfaces/TypeAndVersionInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface TypeAndVersionInterface{ 5 | function typeAndVersion() 6 | external 7 | pure 8 | returns (string memory); 9 | } -------------------------------------------------------------------------------- /contract2/lib/ConfigDigestUtilEVMSimple.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | 5 | /// @title ConfigDigestUtilEVMSimple 6 | /// @notice ConfigDigest related utility functions for "EVMSimple" config 7 | /// digester 8 | library ConfigDigestUtilEVMSimple { 9 | 10 | function configDigestFromConfigData( 11 | uint256 chainId, 12 | address contractAddress, 13 | uint64 configCount, 14 | address[] memory signers, 15 | address[] memory transmitters, 16 | uint8 f, 17 | bytes memory onchainConfig, 18 | uint64 offchainConfigVersion, 19 | bytes memory offchainConfig 20 | ) internal pure returns (bytes32) 21 | { 22 | uint256 hash = uint256( 23 | keccak256( 24 | abi.encode( 25 | chainId, 26 | contractAddress, 27 | configCount, 28 | signers, 29 | transmitters, 30 | f, 31 | onchainConfig, 32 | offchainConfigVersion, 33 | offchainConfig 34 | ))); 35 | 36 | uint256 prefixMask = type(uint256).max << (256-16); // 0xFFFF00..00 37 | uint256 prefix = 0x0001 << (256-16); // 0x000100..00 38 | 39 | return bytes32((prefix & prefixMask) | (hash & ~prefixMask)); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /contract3/OCR3AttestationVerifierBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | /// @title Abstract base contract for verification of OCR3 report 5 | /// @dev Defines the main interface functions to be implemented by signature specific contract variants. 6 | /// @dev Defines the common method used to compute a Keccak-256 digest over a report and its context information. 7 | abstract contract OCR3AttestationVerifierBase { 8 | /// @notice Verifies and stores the list of provided public keys for later use within `verifyAttestation(...)`. 9 | /// Reverts if an invalid number of keys was provided or any key is found invalid. 10 | /// @param n The number of keys expected to be set by this call. Must match the actual number of keys present in 11 | /// the `keys` parameter. The maximum number of keys supported is 32 (based on the width of the 12 | /// attribution bitmask). 13 | /// @param keys A concatenation of `n` public keys. The exact format of the key depends on the signature scheme used 14 | /// for verification (for example, for ECDSA, addresses of 20 bytes each would be used). 15 | function _setVerificationKeys(uint8 n, bytes calldata keys) internal virtual; 16 | 17 | /// @notice Verifies the attestation for the given report. 18 | /// Reverts if the attestation could not be verified successfully. 19 | /// @param configDigest configuration digest of this configuration 20 | /// @param n The total number of oracles. Used to verify the attribution bitmask as part of the attestation data. 21 | /// @param f Maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly. 22 | /// An attestation generated from exactly `f + 1` signatures from different oracles is expected. 23 | /// @param report report data to be attested 24 | /// @param attestation the attestation data for the reports 25 | function _verifyAttestation( 26 | bytes32 configDigest, 27 | uint64 seqNr, 28 | uint8 n, 29 | uint8 f, 30 | bytes memory report, 31 | bytes calldata attestation 32 | ) internal virtual; 33 | 34 | /// @notice Compute the cryptographic hash over the given report in combination with the configuration digest and 35 | /// sequence number. 36 | /// @param configDigest configuration digest of this configuration 37 | /// @param seqNr the sequence number the given report was attested in 38 | /// @param report the report data 39 | function _hashReport(bytes32 configDigest, uint64 seqNr, bytes memory report) internal pure returns (bytes32) { 40 | return keccak256(abi.encode(configDigest, seqNr, keccak256(report))); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contract3/OCR3AttestationVerifierErrors.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | // Raised when the number of provided verifications keys does not match the expected number of keys (parameter: n). 5 | error InvalidNumberOfKeys(); 6 | 7 | // Raised when the provided verifications keys are of invalid size. 8 | error KeysOfInvalidSize(); 9 | 10 | // Raised when an attempt to set more than 32 verification keys is made. 11 | // An upper limit of 32 keys is enforced by the width of the bitmask used for the attribution data. 12 | error MaximumNumberOfKeysExceeded(); 13 | 14 | // Raised when a provided verification key is found invalid. 15 | // Potential causes for invalid keys are, for example: 16 | // - ECDSA: the value 0x0000000000000000000000000000000000000000 17 | // - BLS: a key with an invalid proof-of-possession 18 | error InvalidKey(); 19 | 20 | // Raised when the signature verification failed for the provided attestation. 21 | error InvalidAttestation(); 22 | 23 | // Raised when the provided attestation contains (or is composed of) an invalid number of signatures. 24 | error InvalidAttestationNumberOfSignatures(); 25 | 26 | // Raised when a provided attestation is of invalid size. 27 | // The expected size depends on the signature scheme used, for example: 28 | // - ECDSA: 4 + 64*(f+1) bytes 29 | // - BLS: 37 bytes 30 | error InvalidAttestationLength(); 31 | 32 | // Raised when the attribution bitmask of a given attestation is invalid. 33 | // Exactly f+1 of the least significant n bits must be set. 34 | error InvalidAttestationAttributionBitmask(); 35 | -------------------------------------------------------------------------------- /contract3/OCR3BLSAttestationVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "./OCR3AttestationVerifierBase.sol"; 5 | import "./OCR3BLSAttestationVerifierLib.sol"; 6 | 7 | /// @title Base contract for OCR3 report verification using BLS signatures 8 | /// @dev An application contract should inherit from this contract to compile the provided functionality into the 9 | /// application contract. 10 | /// @dev To instead use a dynamically-dispatched (pre-deployed) variant, see OCR3DynamicallyDispatchedAttestationVerifier. 11 | contract OCR3BLSAttestationVerifier is OCR3AttestationVerifierBase { 12 | // Reserve storage for up to 32 BLS public keys. The current implementation supports up to 32 keys, limited by the 13 | // width of the attribution bitmask used. Keeping the array size fixed at 32 entries is fine for smaller 14 | // configurations, storage costs are only payed for the used number of keys. 15 | OCR3BLSAttestationVerifierLib.G2PointAffine[32] s_keys; 16 | 17 | function _setVerificationKeys(uint8 n, bytes calldata keys) internal override { 18 | OCR3BLSAttestationVerifierLib.setVerificationKeys(s_keys, n, keys); 19 | } 20 | 21 | function _verifyAttestation( 22 | bytes32 configDigest, 23 | uint64 seqNr, 24 | uint8 n, 25 | uint8 f, 26 | bytes memory report, 27 | bytes calldata attestation 28 | ) internal view override { 29 | bytes32 reportHash = _hashReport(configDigest, seqNr, report); 30 | OCR3BLSAttestationVerifierLib.verifyAttestation(s_keys, n, f, reportHash, attestation); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contract3/OCR3DynamicallyDispatchedBLSAttestationVerifierLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "./OCR3BLSAttestationVerifierLib.sol"; 5 | 6 | /// @title Shim for the core BLS attestation verifier library, allowing it to be pre-deployed separately. 7 | /// @dev The function modifiers of the main interface functions are updated from internal to external. 8 | library OCR3DynamicallyDispatchedBLSAttestationVerifierLib { 9 | function setVerificationKeys( 10 | OCR3BLSAttestationVerifierLib.G2PointAffine[32] storage s_verificationKeys, 11 | uint8 n, 12 | bytes calldata keys 13 | ) external { 14 | OCR3BLSAttestationVerifierLib.setVerificationKeys(s_verificationKeys, n, keys); 15 | } 16 | 17 | function verifyAttestation( 18 | OCR3BLSAttestationVerifierLib.G2PointAffine[32] storage s_verificationKeys, 19 | uint8 n, 20 | uint8 f, 21 | bytes32 reportHash, 22 | bytes calldata attestation 23 | ) external view { 24 | OCR3BLSAttestationVerifierLib.verifyAttestation(s_verificationKeys, n, f, reportHash, attestation); 25 | } 26 | 27 | // Function to initialize the selectors for delegate-calling into this library. 28 | // Derived using keccak256 from the function signatures (without parameter names): 29 | // - keccak256("setVerificationKeys(OCR3BLSAttestationVerifierLib.G2PointAffine[32] storage,uint8,bytes)")[:4] 30 | // - keccak256( 31 | // "verifyAttestation(OCR3BLSAttestationVerifierLib.G2PointAffine[32] storage,uint8,uint8,bytes32,bytes)" 32 | // )[:4] 33 | function getSelectors() external pure returns (bytes4, bytes4) { 34 | return ( 35 | OCR3DynamicallyDispatchedBLSAttestationVerifierLib.setVerificationKeys.selector, 36 | OCR3DynamicallyDispatchedBLSAttestationVerifierLib.verifyAttestation.selector 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /contract3/OCR3DynamicallyDispatchedECDSAAttestationVerifierLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "./OCR3ECDSAAttestationVerifierLib.sol"; 5 | 6 | /// @title Shim for the core ECDSA attestation verifier library, allowing it to be pre-deployed separately. 7 | /// @dev The function modifiers of the main interface functions are updated from internal to external. 8 | library OCR3DynamicallyDispatchedECDSAAttestationVerifierLib { 9 | function setVerificationKeys(uint256[32] storage s_keys, uint8 n, bytes calldata keys) external { 10 | OCR3ECDSAAttestationVerifierLib.setVerificationKeys(s_keys, n, keys); 11 | } 12 | 13 | function verifyAttestation( 14 | uint256[32] storage s_keys, 15 | uint8 n, 16 | uint8 f, 17 | bytes32 reportHash, 18 | bytes calldata attestation 19 | ) external view { 20 | OCR3ECDSAAttestationVerifierLib.verifyAttestation(s_keys, n, f, reportHash, attestation); 21 | } 22 | 23 | // Function to initialize the selectors for delegate-calling into this library. 24 | // Derived using keccak256 from the function signatures (without parameter names): 25 | // - keccak256("setVerificationKeys(uint256[32] storage,uint8,bytes)")[:4] 26 | // - keccak256("verifyAttestation(uint256[32] storage,uint8,uint8,bytes32,bytes)")[:4] 27 | function getSelectors() external pure returns (bytes4, bytes4) { 28 | return ( 29 | OCR3DynamicallyDispatchedECDSAAttestationVerifierLib.setVerificationKeys.selector, 30 | OCR3DynamicallyDispatchedECDSAAttestationVerifierLib.verifyAttestation.selector 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contract3/OCR3ECDSAAttestationVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "./OCR3AttestationVerifierBase.sol"; 5 | import "./OCR3ECDSAAttestationVerifierLib.sol"; 6 | 7 | /// @title Base contract for OCR3 report verification using ECDSA signatures 8 | /// @dev An application contract should inherit from this contract to compile the provided functionality into the 9 | /// application contract. 10 | /// @dev To instead use a dynamically-dispatched (pre-deployed) variant, see 11 | /// OCR3DynamicallyDispatchedAttestationVerifier. 12 | contract OCR3ECDSAAttestationVerifier is OCR3AttestationVerifierBase { 13 | // Reserve storage for up to 32 ECDSA public keys (i.e., the oracle's addresses). The current implementation 14 | // supports up to 32 keys, limited by the width of the attribution bitmask used. Keeping the array size fixed at 32 15 | // entries is fine for smaller configurations, storage costs are only payed for the used number of keys. 16 | uint256[32] s_keys; 17 | 18 | function _setVerificationKeys(uint8 n, bytes calldata keys) internal override { 19 | OCR3ECDSAAttestationVerifierLib.setVerificationKeys(s_keys, n, keys); 20 | } 21 | 22 | function _verifyAttestation( 23 | bytes32 configDigest, 24 | uint64 seqNr, 25 | uint8 n, 26 | uint8 f, 27 | bytes memory report, 28 | bytes calldata attestation 29 | ) internal view override { 30 | bytes32 reportHash = _hashReport(configDigest, seqNr, report); 31 | OCR3ECDSAAttestationVerifierLib.verifyAttestation(s_keys, n, f, reportHash, attestation); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contract3/dev/DemoBLSAttestationVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.19; 3 | 4 | import "../OCR3BLSAttestationVerifier.sol"; 5 | 6 | /// @title Demonstration contract showcasing the new BLS signature verification library for OCR 7 | /// @dev In this example contract, the verification library is compiled into the contract and called directly via the 8 | /// exposed verification function from the base contract. 9 | /// @dev !!! CAUTION !!! 10 | /// This demonstration contract only showcases a subset of the required features needed for a secure 11 | /// implementation. For example, it does not provide access control for the setConfig(...) function used to 12 | /// initialize the verification keys. As such, the demonstration contract - by itself - is NOT secure. 13 | contract DemoBLSAttestationVerifier is OCR3BLSAttestationVerifier { 14 | // A block of storage variables used on the main contract path. 15 | // To be retrieved via a single SLOAD instruction. 16 | HotVars s_hotVars; 17 | 18 | struct HotVars { 19 | uint32 configVersion; 20 | uint8 n; // total number of oracles 21 | uint8 f; // maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly 22 | } 23 | 24 | function setConfig(uint32 configVersion, uint8 n, uint8 f, bytes calldata keys) external { 25 | s_hotVars = HotVars({configVersion: configVersion, n: n, f: f}); 26 | _setVerificationKeys(n, keys); 27 | } 28 | 29 | // We may want to load configDigest from storage instead. 30 | function transmit(bytes32 configDigest, uint64 seqNr, bytes memory report, bytes calldata attestation) external { 31 | // Load s_hotVars once here and then only use hotVars to avoid repeated storage access. 32 | HotVars memory hotVars = s_hotVars; 33 | 34 | // Invoke the attestation verification library. 35 | // The call reverts if the given attestation for the report is invalid. 36 | _verifyAttestation(configDigest, seqNr, hotVars.n, hotVars.f, report, attestation); 37 | 38 | // Update the hotVars in storage. Should be done only when they have been modified. 39 | // Here to silence the warning regarding 'function state mutability'. 40 | s_hotVars = hotVars; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contract3/dev/DemoDynamicallyDispatchedAttestationVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.19; 3 | 4 | import "../OCR3DynamicallyDispatchedAttestationVerifier.sol"; 5 | 6 | /// @title Demonstration contract showcasing the new ECDSA/BLS signature verification library for OCR 7 | /// @dev In this example contract, a pre-deployed verification library (for the chosen signature scheme) is called 8 | /// dynamically via a delegate call through the exposed verification function from the base contract. 9 | /// @dev !!! CAUTION !!! 10 | /// This demonstration contract only showcases a subset of the required features needed for a secure 11 | /// implementation. For example, it does not provide access control for the setConfig(...) function used to 12 | /// initialize the verification keys. As such, the demonstration contract - by itself - is NOT secure. 13 | contract DemoDynamicallyDispatchedAttestationVerifier is OCR3DynamicallyDispatchedAttestationVerifier { 14 | // A block of storage variables used on the main contract path. 15 | // To be retrieved via a single SLOAD instruction. 16 | HotVars s_hotVars; 17 | 18 | struct HotVars { 19 | uint32 configVersion; 20 | uint8 n; // total number of oracles 21 | uint8 f; // maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly 22 | } 23 | 24 | constructor(address verifierLibraryAddress) OCR3DynamicallyDispatchedAttestationVerifier(verifierLibraryAddress) {} 25 | 26 | function setConfig(uint32 configVersion, uint8 n, uint8 f, bytes calldata keys) external { 27 | s_hotVars = HotVars({configVersion: configVersion, n: n, f: f}); 28 | _setVerificationKeys(n, keys); 29 | } 30 | 31 | // We may want to load configDigest from storage instead. 32 | function transmit(bytes32 configDigest, uint64 seqNr, bytes memory report, bytes calldata attestation) external { 33 | // Load s_hotVars once here and then only use hotVars to avoid repeated storage access. 34 | HotVars memory hotVars = s_hotVars; 35 | 36 | // Invoke the attestation verification library. 37 | // The call reverts if the given attestation for the report is invalid. 38 | _verifyAttestation(configDigest, seqNr, hotVars.n, hotVars.f, report, attestation); 39 | 40 | // Update the hotVars in storage. Should be done only when they have been modified. 41 | s_hotVars = hotVars; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /contract3/dev/DemoECDSAAttestationVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.19; 3 | 4 | import "../OCR3ECDSAAttestationVerifier.sol"; 5 | 6 | /// @title Demonstration contract showcasing the new ECDSA signature verification library for OCR 7 | /// @dev In this example contract, the verification library is compiled into the contract and called directly via the 8 | /// exposed verification function from the base contract. 9 | /// @dev !!! CAUTION !!! 10 | /// This demonstration contract only showcases a subset of the required features needed for a secure 11 | /// implementation. For example, it does not provide access control for the setConfig(...) function used to 12 | /// initialize the verification keys. As such, the demonstration contract - by itself - is NOT secure. 13 | contract DemoECDSAAttestationVerifier is OCR3ECDSAAttestationVerifier { 14 | // A block of storage variables used on the main contract path. 15 | // To be retrieved via a single SLOAD instruction. 16 | HotVars s_hotVars; 17 | 18 | struct HotVars { 19 | uint32 configVersion; 20 | uint8 n; // total number of oracles 21 | uint8 f; // maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly 22 | } 23 | 24 | function setConfig(uint32 configVersion, uint8 n, uint8 f, bytes calldata keys) external { 25 | s_hotVars = HotVars({configVersion: configVersion, n: n, f: f}); 26 | _setVerificationKeys(n, keys); 27 | } 28 | 29 | // We may want to load configDigest from storage instead. 30 | function transmit(bytes32 configDigest, uint64 seqNr, bytes memory report, bytes calldata attestation) external { 31 | // Load s_hotVars once here and then only use hotVars to avoid repeated storage access. 32 | HotVars memory hotVars = s_hotVars; 33 | 34 | // Invoke the attestation verification library. 35 | // The call reverts if the given attestation for the report is invalid. 36 | _verifyAttestation(configDigest, seqNr, hotVars.n, hotVars.f, report, attestation); 37 | 38 | // Update the hotVars in storage. Should be done only when they have been modified. 39 | // Here to silence the warning regarding 'function state mutability'. 40 | s_hotVars = hotVars; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /gethwrappers/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | // this is a tools.go file for pinning tool versions as recommended by https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module and 5 | // https://github.com/golang/go/issues/25922#issuecomment-413898264 6 | 7 | package tools 8 | 9 | import ( 10 | _ "github.com/ethereum/go-ethereum/cmd/abigen" 11 | ) 12 | -------------------------------------------------------------------------------- /gethwrappers2/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | // this is a tools.go file for pinning tool versions as recommended by https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module and 5 | // https://github.com/golang/go/issues/25922#issuecomment-413898264 6 | 7 | package tools 8 | 9 | import ( 10 | _ "github.com/ethereum/go-ethereum/cmd/abigen" 11 | ) 12 | -------------------------------------------------------------------------------- /internal/byzquorum/byzquorum.go: -------------------------------------------------------------------------------- 1 | package byzquorum 2 | 3 | // Size of a byz. quorum. We assume n >= 3*f + 1. 4 | func Size(n, f int) int { 5 | return (n+f)/2 + 1 6 | } 7 | -------------------------------------------------------------------------------- /internal/configdigesthelper/convert.go: -------------------------------------------------------------------------------- 1 | package configdigesthelper 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | 7 | ocr1types "github.com/smartcontractkit/libocr/offchainreporting/types" 8 | ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 9 | ) 10 | 11 | func OCR1ToOCR2(configDigest ocr1types.ConfigDigest) ocr2types.ConfigDigest { 12 | var ocr2ConfigDigest ocr2types.ConfigDigest 13 | binary.BigEndian.PutUint16(ocr2ConfigDigest[:], uint16(ocr2types.ConfigDigestPrefixOCR1)) 14 | if copy(ocr2ConfigDigest[2:], configDigest[:]) != len(configDigest) { 15 | // assertion 16 | panic("copy error") 17 | } 18 | return ocr2ConfigDigest 19 | } 20 | 21 | func OCR2ToOCR1(configDigest ocr2types.ConfigDigest) (ocr1types.ConfigDigest, error) { 22 | if !ocr2types.ConfigDigestPrefixOCR1.IsPrefixOf(configDigest) { 23 | return ocr1types.ConfigDigest{}, fmt.Errorf("configDigest (%v) does not start with OCR1 prefix (%v)", 24 | configDigest, ocr2types.ConfigDigestPrefixOCR1) 25 | } 26 | 27 | var ocr1ConfigDigest ocr1types.ConfigDigest 28 | if copy(ocr1ConfigDigest[:], configDigest[2:2+len(ocr1ConfigDigest)]) != len(ocr1ConfigDigest) { 29 | // assertion 30 | panic("copy error") 31 | } 32 | 33 | for i := 2 + len(ocr1ConfigDigest); i < len(configDigest); i++ { 34 | if configDigest[i] != 0 { 35 | return ocr1types.ConfigDigest{}, fmt.Errorf("configDigest (%v) tail does not consist of all zeros", configDigest) 36 | } 37 | } 38 | 39 | return ocr1ConfigDigest, nil 40 | } 41 | -------------------------------------------------------------------------------- /internal/loghelper/close_log_error.go: -------------------------------------------------------------------------------- 1 | package loghelper 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/smartcontractkit/libocr/commontypes" 7 | ) 8 | 9 | // Closes closer. If an error occurs, it is logged at WARN level together with 10 | // msg 11 | func CloseLogError(closer io.Closer, logger commontypes.Logger, msg string) { 12 | if err := closer.Close(); err != nil { 13 | logger.Warn(msg, commontypes.LogFields{ 14 | "error": err, 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/loghelper/if_not_stopped.go: -------------------------------------------------------------------------------- 1 | package loghelper 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/smartcontractkit/libocr/subprocesses" 7 | ) 8 | 9 | type IfNotStopped struct { 10 | chStop chan struct{} 11 | subs subprocesses.Subprocesses 12 | } 13 | 14 | // If Stop is called prior to expiry of d, f won't be executed. Otherwise, f 15 | // will be executed and Stop will block until f returns. That makes it different 16 | // from the standard library's time.AfterFunc() whose Stop() function will 17 | // return while f is still running. 18 | func NewIfNotStopped(d time.Duration, f func()) *IfNotStopped { 19 | ins := IfNotStopped{ 20 | make(chan struct{}, 1), 21 | subprocesses.Subprocesses{}, 22 | } 23 | ins.subs.Go(func() { 24 | t := time.NewTimer(d) 25 | defer t.Stop() 26 | select { 27 | case <-t.C: 28 | f() 29 | case <-ins.chStop: 30 | } 31 | }) 32 | return &ins 33 | } 34 | 35 | func (ins *IfNotStopped) Stop() { 36 | select { 37 | case <-ins.chStop: 38 | // chStop has been closed, don't close again 39 | default: 40 | close(ins.chStop) 41 | } 42 | 43 | ins.subs.Wait() 44 | } 45 | -------------------------------------------------------------------------------- /internal/loghelper/taper.go: -------------------------------------------------------------------------------- 1 | package loghelper 2 | 3 | // LogarithmicTaper provides logarithmic tapering of an event sequence. 4 | // For example, if the taper is Triggered 50 times with a function that 5 | // simply prints the provided count, the output would be 1,2,4,8,16,32. 6 | type LogarithmicTaper struct { 7 | count uint64 8 | } 9 | 10 | // Trigger increments a count and calls f iff the new count is a power of two 11 | func (tap *LogarithmicTaper) Trigger(f func(newCount uint64)) { 12 | tap.count++ 13 | if f != nil && isPowerOfTwo(tap.count) { 14 | f(tap.count) 15 | } 16 | } 17 | 18 | // Count returns the internal count of the taper 19 | func (tap *LogarithmicTaper) Count() uint64 { 20 | return tap.count 21 | } 22 | 23 | // Reset resets the count to 0 and then calls f with the previous count 24 | // iff it wasn't already 0 25 | func (tap *LogarithmicTaper) Reset(f func(oldCount uint64)) { 26 | if tap.count != 0 { 27 | oldCount := tap.count 28 | tap.count = 0 29 | f(oldCount) 30 | } 31 | } 32 | 33 | func isPowerOfTwo(num uint64) bool { 34 | return num != 0 && (num&(num-1)) == 0 35 | } 36 | -------------------------------------------------------------------------------- /internal/metricshelper/registerer.go: -------------------------------------------------------------------------------- 1 | package metricshelper 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | "github.com/smartcontractkit/libocr/commontypes" 8 | ) 9 | 10 | type PrometheusRegistererWrapper struct { 11 | registerer prometheus.Registerer 12 | logger commontypes.Logger 13 | } 14 | 15 | func NewPrometheusRegistererWrapper(registerer prometheus.Registerer, logger commontypes.Logger) *PrometheusRegistererWrapper { 16 | prw := &PrometheusRegistererWrapper{ 17 | registerer: registerer, 18 | logger: logger, 19 | } 20 | return prw 21 | } 22 | 23 | var _ prometheus.Registerer = (*PrometheusRegistererWrapper)(nil) 24 | 25 | func (prw *PrometheusRegistererWrapper) Register(collector prometheus.Collector) error { 26 | prw.logger.Trace("Registering collector", nil) 27 | if collector == nil { 28 | return fmt.Errorf("tried to register nil collector") 29 | } 30 | if prw.registerer == nil { 31 | return fmt.Errorf("nil registerer implementation") 32 | } 33 | return prw.registerer.Register(collector) 34 | } 35 | 36 | func (prw *PrometheusRegistererWrapper) MustRegister(collectors ...prometheus.Collector) { 37 | prw.logger.Critical("Should use the Register method instead! Registering collectors with MustRegister will panic if not successful", nil) 38 | prw.registerer.MustRegister(collectors...) 39 | } 40 | 41 | func (prw *PrometheusRegistererWrapper) Unregister(collector prometheus.Collector) bool { 42 | prw.logger.Trace("Unregistering collector", nil) 43 | if collector == nil { 44 | prw.logger.Warn("Unregistering nil collector", nil) 45 | return false 46 | } 47 | if prw.registerer == nil { 48 | prw.logger.Warn("Nil registerer implementation", nil) 49 | return false 50 | } 51 | return prw.registerer.Unregister(collector) 52 | } 53 | 54 | func RegisterOrLogError(logger commontypes.Logger, 55 | registerer prometheus.Registerer, 56 | collector prometheus.Collector, 57 | name string, 58 | ) { 59 | if err := registerer.Register(collector); err != nil { 60 | logger.Error("PrometheusMetrics: Could not register collector", 61 | commontypes.LogFields{"name": name, "error": err}) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /internal/peerkeyringhelper/ed25519_sanity_check.go: -------------------------------------------------------------------------------- 1 | package peerkeyringhelper 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ed25519" 6 | "fmt" 7 | ) 8 | 9 | func ed25519SanityCheck(maybeEd25519 ed25519.PrivateKey) error { 10 | if len(maybeEd25519) != ed25519.PrivateKeySize { 11 | return fmt.Errorf("invalid key size for ed25519 private key, was %d, expected %d", len(maybeEd25519), ed25519.PrivateKeySize) 12 | } 13 | 14 | // this could conceivably panic but since the length was correct on the private key it will not 15 | seed := maybeEd25519.Seed() 16 | 17 | // this could conceivably panic but the seed returned by Seed() should be fine 18 | sk := ed25519.NewKeyFromSeed(seed) 19 | 20 | if !bytes.Equal(sk, maybeEd25519) { 21 | return fmt.Errorf("private key produced by seed (%x) and private key (%x) provided differ", sk, maybeEd25519) 22 | } 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /internal/peerkeyringhelper/peer_keyring_with_private_key.go: -------------------------------------------------------------------------------- 1 | package peerkeyringhelper 2 | 3 | import ( 4 | "crypto/ed25519" 5 | "fmt" 6 | 7 | ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" 8 | ) 9 | 10 | type PeerKeyringWithPrivateKey struct { 11 | privateKey ed25519.PrivateKey 12 | peerPublicKey ragetypes.PeerPublicKey 13 | } 14 | 15 | var _ ragetypes.PeerKeyring = &PeerKeyringWithPrivateKey{} 16 | 17 | func NewPeerKeyringWithPrivateKey(privateKey ed25519.PrivateKey) (*PeerKeyringWithPrivateKey, error) { 18 | if err := ed25519SanityCheck(privateKey); err != nil { 19 | return nil, fmt.Errorf("ed25519 sanity check failed: %w", err) 20 | } 21 | peerPublicKey, err := ragetypes.PeerPublicKeyFromGenericPublicKey(privateKey.Public()) 22 | if err != nil { 23 | return nil, fmt.Errorf("StaticallySizedEd25519PublicKey failed even though sanity check succeeded: %w", err) 24 | } 25 | return &PeerKeyringWithPrivateKey{privateKey, peerPublicKey}, nil 26 | } 27 | 28 | // PublicKey implements ragetypes.PeerKeyring. 29 | func (s *PeerKeyringWithPrivateKey) PublicKey() ragetypes.PeerPublicKey { 30 | return s.peerPublicKey 31 | } 32 | 33 | // Sign implements ragetypes.PeerKeyring. 34 | func (s *PeerKeyringWithPrivateKey) Sign(msg []byte) (signature []byte, err error) { 35 | return ed25519.Sign(s.privateKey, msg), nil 36 | } 37 | -------------------------------------------------------------------------------- /internal/util/generic.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | func PointerTo[T any](v T) *T { 4 | return &v 5 | } 6 | 7 | func NilCoalesce[T any](maybe *T, default_ T) T { 8 | if maybe != nil { 9 | return *maybe 10 | } else { 11 | return default_ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /networking/bootstrapper_v2.go: -------------------------------------------------------------------------------- 1 | package networking 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "sync" 7 | 8 | "github.com/smartcontractkit/libocr/commontypes" 9 | ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 10 | ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" 11 | 12 | "github.com/smartcontractkit/libocr/internal/loghelper" 13 | ) 14 | 15 | var ( 16 | _ commontypes.Bootstrapper = &bootstrapperV2{} 17 | ) 18 | 19 | type bootstrapperState int 20 | 21 | const ( 22 | _ bootstrapperState = iota 23 | bootstrapperUnstarted 24 | bootstrapperStarted 25 | bootstrapperClosed 26 | ) 27 | 28 | type bootstrapperV2 struct { 29 | logger loghelper.LoggerWithContext 30 | configDigest ocr2types.ConfigDigest 31 | registration io.Closer 32 | state bootstrapperState 33 | 34 | stateMu *sync.Mutex 35 | } 36 | 37 | func newBootstrapperV2( 38 | logger loghelper.LoggerWithContext, 39 | configDigest ocr2types.ConfigDigest, 40 | v2peerIDs []ragetypes.PeerID, 41 | v2bootstrappers []ragetypes.PeerInfo, 42 | registration io.Closer, 43 | ) (*bootstrapperV2, error) { 44 | logger = logger.MakeChild(commontypes.LogFields{ 45 | "id": "bootstrapperV2", 46 | "configDigest": configDigest.Hex(), 47 | }) 48 | 49 | logger.Info("BootstrapperV2: Initialized", commontypes.LogFields{ 50 | "bootstrappers": v2bootstrappers, 51 | "oracles": v2peerIDs, 52 | }) 53 | 54 | return &bootstrapperV2{ 55 | logger, 56 | configDigest, 57 | registration, 58 | bootstrapperUnstarted, 59 | new(sync.Mutex), 60 | }, nil 61 | } 62 | 63 | func (b *bootstrapperV2) Start() error { 64 | succeeded := false 65 | defer func() { 66 | if !succeeded { 67 | b.Close() 68 | } 69 | }() 70 | 71 | b.stateMu.Lock() 72 | defer b.stateMu.Unlock() 73 | 74 | if b.state != bootstrapperUnstarted { 75 | return fmt.Errorf("cannot start bootstrapperV2 that is not unstarted, state was: %d", b.state) 76 | } 77 | 78 | b.state = bootstrapperStarted 79 | 80 | b.logger.Info("BootstrapperV2: Started listening", nil) 81 | succeeded = true 82 | return nil 83 | } 84 | 85 | func (b *bootstrapperV2) Close() error { 86 | b.stateMu.Lock() 87 | defer b.stateMu.Unlock() 88 | if b.state != bootstrapperStarted { 89 | return fmt.Errorf("cannot close bootstrapperV2 that is not started, state was: %d", b.state) 90 | } 91 | b.state = bootstrapperClosed 92 | 93 | if err := b.registration.Close(); err != nil { 94 | return fmt.Errorf("could not unregister bootstrapperV2: %w", err) 95 | } 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /networking/limits.go: -------------------------------------------------------------------------------- 1 | package networking 2 | 3 | type BinaryNetworkEndpointLimits struct { 4 | MaxMessageLength int 5 | MessagesRatePerOracle float64 6 | MessagesCapacityPerOracle int 7 | BytesRatePerOracle float64 8 | BytesCapacityPerOracle int 9 | } 10 | -------------------------------------------------------------------------------- /networking/ocr1_peer.go: -------------------------------------------------------------------------------- 1 | package networking 2 | 3 | import ( 4 | "github.com/smartcontractkit/libocr/commontypes" 5 | "github.com/smartcontractkit/libocr/internal/configdigesthelper" 6 | ocr1types "github.com/smartcontractkit/libocr/offchainreporting/types" 7 | ) 8 | 9 | type ocr1BinaryNetworkEndpointFactory struct { 10 | *concretePeerV2 11 | } 12 | 13 | var _ ocr1types.BinaryNetworkEndpointFactory = (*ocr1BinaryNetworkEndpointFactory)(nil) 14 | 15 | const ( 16 | // MaxOCRMsgLength is the maximum allowed length for a data payload in bytes 17 | // This is exported as serialization tests depend on it. 18 | // NOTE: This is slightly larger than 2x of the largest message we can 19 | // possibly send, assuming N=31. 20 | MaxOCRMsgLength = 10000 21 | ) 22 | 23 | func (o *ocr1BinaryNetworkEndpointFactory) NewEndpoint( 24 | configDigest ocr1types.ConfigDigest, 25 | pids []string, 26 | v2bootstrappers []commontypes.BootstrapperLocator, 27 | f int, 28 | messagesRatePerOracle float64, 29 | messagesCapacityPerOracle int, 30 | ) (commontypes.BinaryNetworkEndpoint, error) { 31 | return o.newEndpoint( 32 | configdigesthelper.OCR1ToOCR2(configDigest), 33 | pids, 34 | v2bootstrappers, 35 | BinaryNetworkEndpointLimits{ 36 | MaxOCRMsgLength, 37 | messagesRatePerOracle, 38 | messagesCapacityPerOracle, 39 | messagesRatePerOracle * MaxOCRMsgLength, 40 | messagesCapacityPerOracle * MaxOCRMsgLength, 41 | }, 42 | ) 43 | } 44 | 45 | type ocr1BootstrapperFactory struct { 46 | *concretePeerV2 47 | } 48 | 49 | func (o *ocr1BootstrapperFactory) NewBootstrapper( 50 | configDigest ocr1types.ConfigDigest, 51 | peerIDs []string, 52 | v2bootstrappers []commontypes.BootstrapperLocator, 53 | f int, 54 | ) (commontypes.Bootstrapper, error) { 55 | return o.newBootstrapper( 56 | configdigesthelper.OCR1ToOCR2(configDigest), 57 | peerIDs, 58 | v2bootstrappers, 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /networking/ocr2_peer.go: -------------------------------------------------------------------------------- 1 | package networking 2 | 3 | import ( 4 | "github.com/smartcontractkit/libocr/commontypes" 5 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 6 | ) 7 | 8 | type ocr2BinaryNetworkEndpointFactory struct { 9 | *concretePeerV2 10 | } 11 | 12 | type ocr2BootstrapperFactory struct { 13 | *concretePeerV2 14 | } 15 | 16 | func (o *ocr2BinaryNetworkEndpointFactory) NewEndpoint( 17 | configDigest types.ConfigDigest, 18 | pids []string, 19 | v2bootstrappers []commontypes.BootstrapperLocator, 20 | f int, 21 | limits types.BinaryNetworkEndpointLimits, 22 | ) (commontypes.BinaryNetworkEndpoint, error) { 23 | return o.newEndpoint( 24 | configDigest, 25 | pids, 26 | v2bootstrappers, 27 | BinaryNetworkEndpointLimits(limits), 28 | ) 29 | } 30 | 31 | func (o *ocr2BootstrapperFactory) NewBootstrapper( 32 | configDigest types.ConfigDigest, 33 | peerIDs []string, 34 | v2bootstrappers []commontypes.BootstrapperLocator, 35 | f int, 36 | ) (commontypes.Bootstrapper, error) { 37 | return o.newBootstrapper( 38 | configDigest, 39 | peerIDs, 40 | v2bootstrappers, 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /networking/ragedisco/autodetect/autodetect.go: -------------------------------------------------------------------------------- 1 | package autodetect 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/netip" 7 | "sort" 8 | ) 9 | 10 | type addrSet struct { 11 | public []netip.Addr 12 | private []netip.Addr 13 | loopback []netip.Addr 14 | } 15 | 16 | func (as *addrSet) add(ip netip.Addr) { 17 | switch { 18 | case ip.IsLoopback(): 19 | as.loopback = append(as.loopback, ip) 20 | case ip.IsPrivate() || ip.IsLinkLocalUnicast(): // we need the second check for IPv6 21 | as.private = append(as.private, ip) 22 | default: 23 | as.public = append(as.public, ip) 24 | } 25 | } 26 | 27 | func sortIPs(ips []netip.Addr) { 28 | sort.Slice(ips, func(i, j int) bool { 29 | return ips[i].String() < ips[j].String() 30 | }) 31 | } 32 | 33 | func (as *addrSet) all() []netip.Addr { 34 | // We sort each subset of IPs because the order in which they are returned from net.InterfaceAddrs can be 35 | // unpredictable. 36 | sortIPs(as.public) 37 | sortIPs(as.private) 38 | sortIPs(as.loopback) 39 | var ips []netip.Addr 40 | // We return IPs in order of decreasing reachability from the outside. 41 | ips = append(ips, as.public...) 42 | ips = append(ips, as.private...) 43 | ips = append(ips, as.loopback...) 44 | return ips 45 | } 46 | 47 | func parseIPFromNetAddr(netAddr string) (netip.Addr, error) { 48 | ipPrefix, err := netip.ParsePrefix(netAddr) 49 | if err != nil { 50 | // NOTE: This might be unnecessary but it seems like the type returned by net.InterfaceAddrs is 51 | // OS-dependent so we don't take any chances. 52 | return netip.ParseAddr(netAddr) 53 | } else { 54 | return ipPrefix.Addr(), nil 55 | } 56 | } 57 | 58 | func prioritizeNetAddrs(netAddrs []string) ([]netip.Addr, []netip.Addr, error) { 59 | var v4, v6 addrSet 60 | 61 | for _, netAddr := range netAddrs { 62 | ip, err := parseIPFromNetAddr(netAddr) 63 | if err != nil { 64 | return nil, nil, fmt.Errorf("failed to parse IP (%q): %w", netAddr, err) 65 | } 66 | if ip.Is4() { 67 | v4.add(ip) 68 | } else if ip.Is6() { 69 | v6.add(ip) 70 | } else { 71 | return nil, nil, fmt.Errorf("invalid ip: %s", ip) 72 | } 73 | } 74 | 75 | return v4.all(), v6.all(), nil 76 | } 77 | 78 | func AutodetectIPs() ([]netip.Addr, []netip.Addr, error) { 79 | netAddrs, err := net.InterfaceAddrs() 80 | if err != nil { 81 | return nil, nil, err 82 | } 83 | var addrs []string 84 | for _, netAddr := range netAddrs { 85 | addrs = append(addrs, netAddr.String()) 86 | } 87 | return prioritizeNetAddrs(addrs) 88 | } 89 | -------------------------------------------------------------------------------- /networking/ragedisco/connectivity.go: -------------------------------------------------------------------------------- 1 | package ragedisco 2 | 3 | import ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" 4 | 5 | type connectivityMsgType int 6 | 7 | const ( 8 | _ connectivityMsgType = iota 9 | connectivityAdd 10 | connectivityRemove 11 | ) 12 | 13 | type connectivityMsg struct { 14 | msgType connectivityMsgType 15 | peerID ragetypes.PeerID 16 | } 17 | -------------------------------------------------------------------------------- /networking/ragedisco/group.go: -------------------------------------------------------------------------------- 1 | package ragedisco 2 | 3 | import ( 4 | ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" 5 | ) 6 | 7 | type group struct { 8 | oracleNodes []ragetypes.PeerID 9 | bootstrapperNodes []ragetypes.PeerInfo 10 | } 11 | 12 | func (g *group) oracleIDs() []ragetypes.PeerID { 13 | return g.oracleNodes 14 | } 15 | 16 | func (g *group) bootstrapperIDs() (ps []ragetypes.PeerID) { 17 | for _, inf := range g.bootstrapperNodes { 18 | ps = append(ps, inf.ID) 19 | } 20 | return 21 | } 22 | 23 | func (g *group) peerIDs() []ragetypes.PeerID { 24 | return append(g.oracleIDs(), g.bootstrapperIDs()...) 25 | } 26 | 27 | func (g *group) hasOracle(hpid ragetypes.PeerID) bool { 28 | for _, pid := range g.oracleNodes { 29 | if pid == hpid { 30 | return true 31 | } 32 | } 33 | return false 34 | } 35 | -------------------------------------------------------------------------------- /networking/ragedisco/helpers.go: -------------------------------------------------------------------------------- 1 | package ragedisco 2 | 3 | import ( 4 | "github.com/smartcontractkit/libocr/commontypes" 5 | ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" 6 | ) 7 | 8 | func equalAddrs(a []ragetypes.Address, b []ragetypes.Address) bool { 9 | if len(a) != len(b) { 10 | return false 11 | } 12 | for i := range a { 13 | if a[i] != b[i] { 14 | return false 15 | } 16 | } 17 | return true 18 | } 19 | 20 | func reason(err error) commontypes.LogFields { 21 | return commontypes.LogFields{"error": err} 22 | } 23 | -------------------------------------------------------------------------------- /networking/ragedisco/metrics.go: -------------------------------------------------------------------------------- 1 | package ragedisco 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | "github.com/smartcontractkit/libocr/commontypes" 6 | "github.com/smartcontractkit/libocr/internal/metricshelper" 7 | "github.com/smartcontractkit/libocr/ragep2p/types" 8 | ) 9 | 10 | type discoveryProtocolMetrics struct { 11 | registerer prometheus.Registerer 12 | registeredPeers prometheus.Gauge 13 | discoveredPeers prometheus.Gauge 14 | bootstrappers prometheus.Gauge 15 | } 16 | 17 | func newDiscoveryProtocolMetrics(registerer prometheus.Registerer, logger commontypes.Logger, peerID types.PeerID) *discoveryProtocolMetrics { 18 | labels := map[string]string{"peer_id": peerID.String()} 19 | 20 | registeredPeers := prometheus.NewGauge(prometheus.GaugeOpts{ 21 | Name: "ragedisco_registered_peers", 22 | Help: "The number of registered peers in peer discovery", 23 | ConstLabels: labels, 24 | }) 25 | 26 | metricshelper.RegisterOrLogError(logger, registerer, registeredPeers, "ragedisco_registered_peers") 27 | 28 | discoveredPeers := prometheus.NewGauge(prometheus.GaugeOpts{ 29 | Name: "ragedisco_discovered_peers", 30 | Help: "The number of discovered peers in peer discovery. A peer " + 31 | "is considered discovered if we have obtained a valid announcement for it. " + 32 | "Note that a valid announcement may still contain an incorrect address.", 33 | ConstLabels: labels, 34 | }) 35 | 36 | metricshelper.RegisterOrLogError(logger, registerer, discoveredPeers, "ragedisco_discovered_peers") 37 | 38 | bootstrappers := prometheus.NewGauge(prometheus.GaugeOpts{ 39 | Name: "ragedisco_bootstappers", 40 | Help: "The number of bootstrappers in peer discovery", 41 | ConstLabels: labels, 42 | }) 43 | 44 | metricshelper.RegisterOrLogError(logger, registerer, bootstrappers, "ragedisco_bootstappers") 45 | 46 | return &discoveryProtocolMetrics{ 47 | registerer, 48 | registeredPeers, 49 | discoveredPeers, 50 | bootstrappers, 51 | } 52 | } 53 | 54 | func (dpm *discoveryProtocolMetrics) Close() { 55 | dpm.registerer.Unregister(dpm.registeredPeers) 56 | dpm.registerer.Unregister(dpm.bootstrappers) 57 | dpm.registerer.Unregister(dpm.discoveredPeers) 58 | } 59 | -------------------------------------------------------------------------------- /networking/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "context" 4 | 5 | type DiscovererDatabase interface { 6 | // StoreAnnouncement has key-value-store semantics and stores a peerID (key) and an associated serialized 7 | //announcement (value). 8 | StoreAnnouncement(ctx context.Context, peerID string, ann []byte) error 9 | 10 | // ReadAnnouncements returns one serialized announcement (if available) for each of the peerIDs in the form of a map 11 | // keyed by each announcement's corresponding peer ID. 12 | ReadAnnouncements(ctx context.Context, peerIDs []string) (map[string][]byte, error) 13 | } 14 | -------------------------------------------------------------------------------- /offchainreporting/doc.go: -------------------------------------------------------------------------------- 1 | // Package offchainreporting implements the Chainlink Offchain Reporting Protocol 2 | // 3 | // A note about concurrency 4 | // 5 | // We spawn lots of goroutines in this package. As a general rule, we keep track 6 | // of all of them using the subprocesses package. We typically signal shutdowns 7 | // using contexts and then do a subprocesses.Wait() to ensure that we're not 8 | // leaking goroutines. 9 | 10 | package offchainreporting 11 | -------------------------------------------------------------------------------- /offchainreporting/internal/config/abiencode.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // setConfigEncodedComponentsABI specifies the serialization schema for the 4 | // encoded config, in a form which can be parsed by abigen. The "name" of each 5 | // component must match the name of the corresponding field in 6 | // setConfigSerializationTypes, and the "name" of each component in 7 | // "sharedSecretEncryptions" must match the name of the corresponding field in 8 | // sseSerializationTypes. 9 | const setConfigEncodedComponentsABI = `[ 10 | { 11 | "name": "setConfigEncodedComponents", 12 | "type": "tuple", 13 | "components": [ 14 | { 15 | "name": "deltaProgress", 16 | "type": "int64" 17 | }, 18 | { 19 | "name": "deltaResend", 20 | "type": "int64" 21 | }, 22 | { 23 | "name": "deltaRound", 24 | "type": "int64" 25 | }, 26 | { 27 | "name": "deltaGrace", 28 | "type": "int64" 29 | }, 30 | { 31 | "name": "deltaC", 32 | "type": "int64" 33 | }, 34 | { 35 | "name": "alphaPPB", 36 | "type": "uint64" 37 | }, 38 | { 39 | "name": "deltaStage", 40 | "type": "int64" 41 | }, 42 | { 43 | "name": "rMax", 44 | "type": "uint8" 45 | }, 46 | { 47 | "name": "s", 48 | "type": "uint8[]" 49 | }, 50 | { 51 | "name": "offchainPublicKeys", 52 | "type": "bytes32[]" 53 | }, 54 | { 55 | "name": "peerIDs", 56 | "type": "string" 57 | }, 58 | { 59 | "name": "sharedSecretEncryptions", 60 | "type": "tuple", 61 | "components": [ 62 | { 63 | "name": "diffieHellmanPoint", 64 | "type": "bytes32" 65 | }, 66 | { 67 | "name": "sharedSecretHash", 68 | "type": "bytes32" 69 | }, 70 | { 71 | "name": "encryptions", 72 | "type": "bytes16[]" 73 | } 74 | ] 75 | } 76 | ] 77 | } 78 | ]` 79 | -------------------------------------------------------------------------------- /offchainreporting/internal/config/config_digest.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi" 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/ethereum/go-ethereum/crypto" 10 | 11 | "github.com/smartcontractkit/libocr/gethwrappers/exposedoffchainaggregator" 12 | 13 | "github.com/smartcontractkit/libocr/offchainreporting/types" 14 | ) 15 | 16 | func makeConfigDigestArgs() abi.Arguments { 17 | abi, err := abi.JSON(strings.NewReader( 18 | exposedoffchainaggregator.ExposedOffchainAggregatorABI)) 19 | if err != nil { 20 | // assertion 21 | panic(fmt.Sprintf("could not parse aggregator ABI: %s", err.Error())) 22 | } 23 | return abi.Methods["exposedConfigDigestFromConfigData"].Inputs 24 | } 25 | 26 | var configDigestArgs = makeConfigDigestArgs() 27 | 28 | func ConfigDigest( 29 | contractAddress common.Address, 30 | configCount uint64, 31 | oracles []common.Address, 32 | transmitters []common.Address, 33 | threshold uint8, 34 | encodedConfigVersion uint64, 35 | config []byte, 36 | ) types.ConfigDigest { 37 | msg, err := configDigestArgs.Pack( 38 | contractAddress, 39 | configCount, 40 | oracles, 41 | transmitters, 42 | threshold, 43 | encodedConfigVersion, 44 | config, 45 | ) 46 | if err != nil { 47 | // assertion 48 | panic(err) 49 | } 50 | rawHash := crypto.Keccak256(msg) 51 | configDigest := types.ConfigDigest{} 52 | if n := copy(configDigest[:], rawHash); n != len(configDigest) { 53 | // assertion 54 | panic("copy too little data") 55 | } 56 | return configDigest 57 | } 58 | -------------------------------------------------------------------------------- /offchainreporting/internal/config/shared_secret_encrypt_xxx.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "crypto/aes" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/ethereum/go-ethereum/crypto" 10 | "github.com/pkg/errors" 11 | "github.com/smartcontractkit/libocr/offchainreporting/types" 12 | "golang.org/x/crypto/curve25519" 13 | ) 14 | 15 | // XXXEncryptSharedSecretInternal constructs a SharedSecretEncryptions from 16 | // a set of SharedSecretEncryptionPublicKeys, the sharedSecret, and an 17 | // ephemeral secret key sk 18 | func XXXEncryptSharedSecretInternal( 19 | publicKeys []types.SharedSecretEncryptionPublicKey, 20 | sharedSecret *[SharedSecretSize]byte, 21 | sk *[32]byte, 22 | ) SharedSecretEncryptions { 23 | pk, err := curve25519.X25519(sk[:], curve25519.Basepoint) 24 | if err != nil { 25 | panic("while encrypting sharedSecret: " + err.Error()) // XXX: return an error/log 26 | } 27 | 28 | var pkArray [32]byte 29 | copy(pkArray[:], pk) 30 | 31 | encryptedSharedSecrets := []encryptedSharedSecret{} 32 | for _, pk := range publicKeys { // encrypt sharedSecret with each pk 33 | pkBytes := [32]byte(pk) 34 | dhPoint, err := curve25519.X25519(sk[:], pkBytes[:]) 35 | if err != nil { 36 | panic("while encrypting sharedSecret: " + err.Error()) // XXX: return an error/log 37 | } 38 | 39 | key := crypto.Keccak256(dhPoint)[:16] 40 | 41 | encryptedSharedSecret := encryptedSharedSecret(aesEncryptBlock(key, sharedSecret[:])) 42 | encryptedSharedSecrets = append(encryptedSharedSecrets, encryptedSharedSecret) 43 | } 44 | 45 | return SharedSecretEncryptions{ 46 | pkArray, 47 | common.BytesToHash(crypto.Keccak256(sharedSecret[:])), 48 | encryptedSharedSecrets, 49 | } 50 | } 51 | 52 | // XXXEncryptSharedSecret constructs a SharedSecretEncryptions from 53 | // a set of SharedSecretEncryptionPublicKeys, the sharedSecret, and a cryptographic 54 | // randomness source 55 | func XXXEncryptSharedSecret( 56 | keys []types.SharedSecretEncryptionPublicKey, 57 | sharedSecret *[SharedSecretSize]byte, 58 | rand io.Reader, 59 | ) SharedSecretEncryptions { 60 | var sk [32]byte 61 | _, err := io.ReadFull(rand, sk[:]) 62 | if err != nil { 63 | panic(errors.Wrapf(err, "could not produce entropy for encryption")) 64 | } 65 | return XXXEncryptSharedSecretInternal(keys, sharedSecret, &sk) 66 | } 67 | 68 | // Encrypt one block with AES-128 69 | func aesEncryptBlock(key, plaintext []byte) [16]byte { 70 | if len(key) != 16 { 71 | panic("key has wrong length") 72 | } 73 | if len(plaintext) != 16 { 74 | panic("ciphertext has wrong length") 75 | } 76 | 77 | cipher, err := aes.NewCipher(key) 78 | if err != nil { 79 | // assertion 80 | panic(fmt.Sprintf("Unexpected error during aes.NewCipher: %v", err)) 81 | } 82 | 83 | var ciphertext [16]byte 84 | cipher.Encrypt(ciphertext[:], plaintext) 85 | return ciphertext 86 | } 87 | -------------------------------------------------------------------------------- /offchainreporting/internal/managed/collect_garbage.go: -------------------------------------------------------------------------------- 1 | package managed 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/smartcontractkit/libocr/commontypes" 9 | "github.com/smartcontractkit/libocr/internal/loghelper" 10 | "github.com/smartcontractkit/libocr/offchainreporting/types" 11 | ) 12 | 13 | const collectInterval = 10 * time.Minute 14 | const olderThan = 24 * time.Hour 15 | 16 | // collectGarbage periodically collects garbage left by old transmission protocol instances 17 | func collectGarbage( 18 | ctx context.Context, 19 | database types.Database, 20 | localConfig types.LocalConfig, 21 | logger loghelper.LoggerWithContext, 22 | ) { 23 | for { 24 | wait := collectInterval + time.Duration(rand.Float64()*5.0*60.0)*time.Second 25 | logger.Info("collectGarbage: going to sleep", commontypes.LogFields{ 26 | "duration": wait, 27 | }) 28 | select { 29 | case <-time.After(wait): 30 | logger.Info("collectGarbage: starting collection of old transmissions", commontypes.LogFields{ 31 | "olderThan": olderThan, 32 | }) 33 | // To make sure the context is not leaked we are wrapping the database query. 34 | func() { 35 | childCtx, childCancel := context.WithTimeout(ctx, localConfig.DatabaseTimeout) 36 | defer childCancel() 37 | err := database.DeletePendingTransmissionsOlderThan(childCtx, time.Now().Add(-olderThan)) 38 | if err != nil { 39 | logger.Info("collectGarbage: error in DeletePendingTransmissionsOlderThan", commontypes.LogFields{ 40 | "error": err, 41 | "olderThan": olderThan, 42 | }) 43 | } else { 44 | logger.Info("collectGarbage: finished collection", nil) 45 | } 46 | }() 47 | case <-ctx.Done(): 48 | logger.Info("collectGarbage: exiting", nil) 49 | return 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /offchainreporting/internal/managed/config_overrider.go: -------------------------------------------------------------------------------- 1 | package managed 2 | 3 | import "github.com/smartcontractkit/libocr/offchainreporting/types" 4 | 5 | var _ types.ConfigOverrider = ConfigOverriderWrapper{} 6 | 7 | // A wrapper around a types.ConfigOverrider that gracefully handles nil ConfigOverriders 8 | type ConfigOverriderWrapper struct { 9 | wrapped types.ConfigOverrider 10 | } 11 | 12 | func (cow ConfigOverriderWrapper) ConfigOverride() *types.ConfigOverride { 13 | if cow.wrapped == nil { 14 | return nil 15 | } 16 | return cow.wrapped.ConfigOverride() 17 | } 18 | -------------------------------------------------------------------------------- /offchainreporting/internal/managed/doc.go: -------------------------------------------------------------------------------- 1 | // Package managed provides "managed" versions of Oracle and BootstrapNode 2 | // that perform garbage collection, track on-chain configuration changes, 3 | // serializes messages to binary, etc... 4 | package managed 5 | -------------------------------------------------------------------------------- /offchainreporting/internal/managed/forward_telemetry.go: -------------------------------------------------------------------------------- 1 | package managed 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/smartcontractkit/libocr/commontypes" 7 | "github.com/smartcontractkit/libocr/internal/loghelper" 8 | "github.com/smartcontractkit/libocr/offchainreporting/internal/serialization/protobuf" 9 | "google.golang.org/protobuf/proto" 10 | ) 11 | 12 | // forwardTelemetry receives monitoring events on chTelemetry, serializes them, and forwards 13 | // them to monitoringEndpoint 14 | func forwardTelemetry( 15 | ctx context.Context, 16 | 17 | logger loghelper.LoggerWithContext, 18 | monitoringEndpoint commontypes.MonitoringEndpoint, 19 | 20 | chTelemetry <-chan *protobuf.TelemetryWrapper, 21 | ) { 22 | for { 23 | select { 24 | case t, ok := <-chTelemetry: 25 | if !ok { 26 | // This isn't supposed to happen, but we still handle this case gracefully, 27 | // just in case... 28 | logger.Error("forwardTelemetry: chTelemetry closed unexpectedly. exiting", nil) 29 | return 30 | } 31 | bin, err := proto.Marshal(t) 32 | if err != nil { 33 | logger.Error("forwardTelemetry: failed to Marshal protobuf", commontypes.LogFields{ 34 | "proto": t, 35 | "error": err, 36 | }) 37 | break 38 | } 39 | if monitoringEndpoint != nil { 40 | monitoringEndpoint.SendLog(bin) 41 | } 42 | case <-ctx.Done(): 43 | logger.Info("forwardTelemetry: exiting", nil) 44 | return 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /offchainreporting/internal/managed/load_from_database.go: -------------------------------------------------------------------------------- 1 | package managed 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/smartcontractkit/libocr/commontypes" 7 | "github.com/smartcontractkit/libocr/internal/loghelper" 8 | "github.com/smartcontractkit/libocr/offchainreporting/types" 9 | ) 10 | 11 | func loadConfigFromDatabase(ctx context.Context, database types.Database, logger loghelper.LoggerWithContext) *types.ContractConfig { 12 | cc, err := database.ReadConfig(ctx) 13 | if err != nil { 14 | logger.ErrorIfNotCanceled("loadConfigFromDatabase: Error during Database.ReadConfig", ctx, commontypes.LogFields{ 15 | "error": err, 16 | }) 17 | return nil 18 | } 19 | 20 | if cc == nil { 21 | logger.Info("loadConfigFromDatabase: Database.ReadConfig returned nil, no configuration to restore", nil) 22 | return nil 23 | } 24 | 25 | return cc 26 | } 27 | -------------------------------------------------------------------------------- /offchainreporting/internal/protocol/common.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | type EpochRound struct { 4 | Epoch uint32 5 | Round uint8 6 | } 7 | 8 | func (x EpochRound) Less(y EpochRound) bool { 9 | return x.Epoch < y.Epoch || (x.Epoch == y.Epoch && x.Round < y.Round) 10 | } 11 | -------------------------------------------------------------------------------- /offchainreporting/internal/protocol/heap.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "container/heap" 5 | 6 | "github.com/smartcontractkit/libocr/offchainreporting/types" 7 | ) 8 | 9 | // Type safe wrapper around MinHeapTimeToContractReportInternal 10 | type MinHeapTimeToPendingTransmission struct { 11 | internal MinHeapTimeToPendingTransmissionInternal 12 | } 13 | 14 | func (h *MinHeapTimeToPendingTransmission) Push(item MinHeapTimeToPendingTransmissionItem) { 15 | heap.Push(&h.internal, item) 16 | } 17 | 18 | func (h *MinHeapTimeToPendingTransmission) Pop() MinHeapTimeToPendingTransmissionItem { 19 | return heap.Pop(&h.internal).(MinHeapTimeToPendingTransmissionItem) 20 | } 21 | 22 | func (h *MinHeapTimeToPendingTransmission) Peek() MinHeapTimeToPendingTransmissionItem { 23 | return h.internal[0] 24 | } 25 | 26 | func (h *MinHeapTimeToPendingTransmission) Len() int { 27 | return h.internal.Len() 28 | } 29 | 30 | type MinHeapTimeToPendingTransmissionItem struct { 31 | types.ReportTimestamp 32 | types.PendingTransmission 33 | } 34 | 35 | // Implements heap.Interface and uses interface{} all over the place. 36 | type MinHeapTimeToPendingTransmissionInternal []MinHeapTimeToPendingTransmissionItem 37 | 38 | func (pq MinHeapTimeToPendingTransmissionInternal) Len() int { return len(pq) } 39 | 40 | func (pq MinHeapTimeToPendingTransmissionInternal) Less(i, j int) bool { 41 | return pq[i].Time.Before(pq[j].Time) 42 | } 43 | 44 | func (pq MinHeapTimeToPendingTransmissionInternal) Swap(i, j int) { 45 | pq[i], pq[j] = pq[j], pq[i] 46 | } 47 | 48 | func (pq *MinHeapTimeToPendingTransmissionInternal) Push(x interface{}) { 49 | item := x.(MinHeapTimeToPendingTransmissionItem) 50 | *pq = append(*pq, item) 51 | } 52 | 53 | func (pq *MinHeapTimeToPendingTransmissionInternal) Pop() interface{} { 54 | old := *pq 55 | n := len(old) 56 | item := old[n-1] 57 | *pq = old[0 : n-1] 58 | return item 59 | } 60 | -------------------------------------------------------------------------------- /offchainreporting/internal/protocol/messagebuffer.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | // MessageBuffer implements a fixed capacity ringbuffer for items of type 4 | // MessageToReportGeneration 5 | type MessageBuffer struct { 6 | start int 7 | length int 8 | buffer []*MessageToReportGeneration 9 | } 10 | 11 | func NewMessageBuffer(cap int) *MessageBuffer { 12 | return &MessageBuffer{ 13 | 0, 14 | 0, 15 | make([]*MessageToReportGeneration, cap), 16 | } 17 | } 18 | 19 | // Peek at the front item 20 | func (rb *MessageBuffer) Peek() *MessageToReportGeneration { 21 | if rb.length == 0 { 22 | return nil 23 | } else { 24 | return rb.buffer[rb.start] 25 | } 26 | } 27 | 28 | // Pop front item 29 | func (rb *MessageBuffer) Pop() *MessageToReportGeneration { 30 | result := rb.Peek() 31 | if result != nil { 32 | rb.buffer[rb.start] = nil 33 | rb.start = (rb.start + 1) % len(rb.buffer) 34 | rb.length-- 35 | } 36 | return result 37 | } 38 | 39 | // Push new item to back. If the additional item would lead 40 | // to the capacity being exceeded, remove the front item first 41 | func (rb *MessageBuffer) Push(msg MessageToReportGeneration) { 42 | if rb.length == len(rb.buffer) { 43 | rb.Pop() 44 | } 45 | rb.buffer[(rb.start+rb.length)%len(rb.buffer)] = &msg 46 | rb.length++ 47 | } 48 | -------------------------------------------------------------------------------- /offchainreporting/internal/protocol/network.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/smartcontractkit/libocr/commontypes" 7 | ) 8 | 9 | // NetworkSender sends messages to other oracles 10 | type NetworkSender interface { 11 | // SendTo(msg, to) sends msg to "to" 12 | SendTo(msg Message, to commontypes.OracleID) 13 | // Broadcast(msg) sends msg to all oracles 14 | Broadcast(msg Message) 15 | } 16 | 17 | // NetworkEndpoint sends & receives messages to/from other oracles 18 | type NetworkEndpoint interface { 19 | NetworkSender 20 | // Receive returns channel which carries all messages sent to this oracle 21 | Receive() <-chan MessageWithSender 22 | 23 | // Start must be called before Receive. Calling Start more than once causes 24 | // panic. 25 | Start() error 26 | 27 | // Close must be called before receive. Close can be called multiple times. 28 | // Close can be called even on an unstarted NetworkEndpoint. 29 | Close() error 30 | } 31 | 32 | // SimpleNetwork is a strawman (in-memory) implementation of the Network 33 | // interface. Network channels are buffered and can queue up to 100 messages 34 | // before blocking. 35 | type SimpleNetwork struct { 36 | chs []chan MessageWithSender // i'th channel models oracle i's network 37 | } 38 | 39 | // NewSimpleNetwork returns a SimpleNetwork for n oracles 40 | func NewSimpleNetwork(n int) *SimpleNetwork { 41 | s := SimpleNetwork{} 42 | for i := 0; i < n; i++ { 43 | s.chs = append(s.chs, make(chan MessageWithSender, 100)) 44 | } 45 | return &s 46 | } 47 | 48 | // Endpoint returns the interface for oracle id's networking facilities 49 | func (net *SimpleNetwork) Endpoint(id commontypes.OracleID) (NetworkEndpoint, error) { 50 | return SimpleNetworkEndpoint{ 51 | net, 52 | id, 53 | }, nil 54 | } 55 | 56 | // SimpleNetworkEndpoint is a strawman (in-memory) implementation of 57 | // NetworkEndpoint, used by SimpleNetwork 58 | type SimpleNetworkEndpoint struct { 59 | net *SimpleNetwork // Reference back to network for all participants 60 | id commontypes.OracleID // Index of oracle this endpoint pertains to 61 | } 62 | 63 | var _ NetworkEndpoint = (*SimpleNetworkEndpoint)(nil) 64 | 65 | // SendTo sends msg to oracle "to" 66 | func (end SimpleNetworkEndpoint) SendTo(msg Message, to commontypes.OracleID) { 67 | log.Printf("[%v] sending to %v: %T\n", end.id, to, msg) 68 | end.net.chs[to] <- MessageWithSender{msg, end.id} 69 | } 70 | 71 | // Broadcast sends msg to all participating oracles 72 | func (end SimpleNetworkEndpoint) Broadcast(msg Message) { 73 | log.Printf("[%v] broadcasting: %T\n", end.id, msg) 74 | for _, ch := range end.net.chs { 75 | ch <- MessageWithSender{msg, end.id} 76 | } 77 | } 78 | 79 | // Receive returns a channel which carries all messages sent to the oracle 80 | func (end SimpleNetworkEndpoint) Receive() <-chan MessageWithSender { 81 | return end.net.chs[end.id] 82 | } 83 | 84 | // Start satisfies the interface 85 | func (SimpleNetworkEndpoint) Start() error { return nil } 86 | 87 | // Close satisfies the interface 88 | func (SimpleNetworkEndpoint) Close() error { return nil } 89 | -------------------------------------------------------------------------------- /offchainreporting/internal/protocol/observation.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | 7 | "github.com/smartcontractkit/libocr/commontypes" 8 | "github.com/smartcontractkit/libocr/offchainreporting/internal/protocol/observation" 9 | "github.com/smartcontractkit/libocr/offchainreporting/internal/signature" 10 | "github.com/smartcontractkit/libocr/offchainreporting/types" 11 | ) 12 | 13 | type SignedObservation struct { 14 | Observation observation.Observation 15 | Signature []byte 16 | } 17 | 18 | func MakeSignedObservation( 19 | observation observation.Observation, 20 | repctx ReportContext, 21 | signer func(msg []byte) (sig []byte, err error), 22 | ) ( 23 | SignedObservation, 24 | error, 25 | ) { 26 | payload := signedObservationWireMessage(repctx, observation) 27 | sig, err := signer(payload) 28 | if err != nil { 29 | return SignedObservation{}, err 30 | } 31 | return SignedObservation{observation, sig}, nil 32 | } 33 | 34 | func (so SignedObservation) Equal(so2 SignedObservation) bool { 35 | return so.Observation.Equal(so2.Observation) && 36 | bytes.Equal(so.Signature, so2.Signature) 37 | } 38 | 39 | func (so SignedObservation) Verify(repctx ReportContext, publicKey types.OffchainPublicKey) error { 40 | if so.Observation.IsMissingValue() { 41 | return errors.New("Observation is missing value") 42 | } 43 | 44 | sigPublicKey := signature.OffchainPublicKey(publicKey) 45 | if !sigPublicKey.Verify(signedObservationWireMessage(repctx, so.Observation), so.Signature) { 46 | return errors.New("SignedObservation has invalid signature") 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func signedObservationWireMessage(repctx ReportContext, observation observation.Observation) []byte { 53 | tag := repctx.DomainSeparationTag() 54 | return append(tag[:], observation.Marshal()...) 55 | } 56 | 57 | type AttributedSignedObservation struct { 58 | SignedObservation SignedObservation 59 | Observer commontypes.OracleID 60 | } 61 | 62 | func (aso AttributedSignedObservation) Equal(aso2 AttributedSignedObservation) bool { 63 | return aso.SignedObservation.Equal(aso2.SignedObservation) && 64 | aso.Observer == aso2.Observer 65 | } 66 | -------------------------------------------------------------------------------- /offchainreporting/internal/protocol/persist/persist.go: -------------------------------------------------------------------------------- 1 | package persist 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/smartcontractkit/libocr/commontypes" 8 | "github.com/smartcontractkit/libocr/internal/loghelper" 9 | "github.com/smartcontractkit/libocr/offchainreporting/types" 10 | ) 11 | 12 | type persistState struct { 13 | ctx context.Context 14 | 15 | chPersist <-chan types.PersistentState 16 | configDigest types.ConfigDigest 17 | database types.Database 18 | databaseTimeout time.Duration 19 | logger loghelper.LoggerWithContext 20 | 21 | writtenState *types.PersistentState 22 | } 23 | 24 | // Persist receives states it should persist to the db through chPersist and 25 | // writes them to database. 26 | func Persist( 27 | ctx context.Context, 28 | chPersist <-chan types.PersistentState, 29 | configDigest types.ConfigDigest, 30 | database types.Database, 31 | databaseTimeout time.Duration, 32 | logger loghelper.LoggerWithContext, 33 | ) { 34 | ps := persistState{ 35 | ctx, 36 | 37 | chPersist, 38 | configDigest, 39 | database, 40 | databaseTimeout, 41 | logger, 42 | 43 | nil, 44 | } 45 | ps.run() 46 | } 47 | 48 | // run gets updates from the outside (through chPersist) in a loop, drains 49 | // chPersist so that it can ignore all but the latest state, and writes the 50 | // latest state to the database if it's new, i.e. differs from the previously 51 | // written state. 52 | func (ps *persistState) run() { 53 | for { 54 | select { 55 | case state, ok := <-ps.chPersist: 56 | if !ok { 57 | ps.logger.Error("Persist: chPersist closed unexpectedly, can no longer persist state. This should *not* happen.", commontypes.LogFields{ 58 | "lastWrittenState": ps.writtenState, 59 | }) 60 | return 61 | } 62 | DrainChannel: 63 | for { 64 | select { 65 | case state, ok = <-ps.chPersist: 66 | if !ok { 67 | ps.logger.Error("Persist: chPersist closed unexpectedly, can no longer persist state. This should *not* happen.", commontypes.LogFields{ 68 | "lastWrittenState": ps.writtenState, 69 | }) 70 | return 71 | } 72 | default: 73 | break DrainChannel 74 | } 75 | } 76 | ps.writeIfNew(state) 77 | 78 | case <-ps.ctx.Done(): 79 | ps.logger.Debug("Persist: exiting", nil) 80 | return 81 | } 82 | } 83 | } 84 | 85 | // writeIfNew writes pendingState to the database, iff pendingState differs from 86 | // the last written state. 87 | func (ps *persistState) writeIfNew(pendingState types.PersistentState) { 88 | if ps.writtenState != nil && pendingState.Equal(*ps.writtenState) { 89 | return 90 | } 91 | 92 | writeCtx, writeCancel := context.WithTimeout(ps.ctx, ps.databaseTimeout) 93 | defer writeCancel() 94 | err := ps.database.WriteState( 95 | writeCtx, 96 | ps.configDigest, 97 | pendingState, 98 | ) 99 | if err != nil { 100 | ps.logger.ErrorIfNotCanceled("Persist: unexpected error while persisting state to database", writeCtx, commontypes.LogFields{ 101 | "error": err, 102 | }) 103 | return 104 | } 105 | 106 | ps.writtenState = &pendingState 107 | } 108 | -------------------------------------------------------------------------------- /offchainreporting/internal/protocol/report_context.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/smartcontractkit/libocr/offchainreporting/types" 7 | ) 8 | 9 | // DomainSeparationTag consists of: 10 | // 11-byte zero padding 11 | // 16-byte configDigest 12 | // 4-byte epoch 13 | // 1-byte round 14 | // It uniquely identifies a message to a particular group-epoch-round tuple. 15 | // It is used in signature verification 16 | type DomainSeparationTag [32]byte 17 | 18 | type ReportContext struct { 19 | ConfigDigest types.ConfigDigest 20 | Epoch uint32 21 | Round uint8 22 | } 23 | 24 | func (r ReportContext) DomainSeparationTag() (d DomainSeparationTag) { 25 | serialization := r.ConfigDigest[:] 26 | serialization = append(serialization, []byte{0, 0, 0, 0}...) 27 | binary.BigEndian.PutUint32(serialization[len(serialization)-4:], r.Epoch) 28 | serialization = append(serialization, byte(r.Round)) 29 | copy(d[11:], serialization) 30 | return d 31 | } 32 | 33 | func (r ReportContext) Equal(r2 ReportContext) bool { 34 | return r.ConfigDigest == r2.ConfigDigest && r.Epoch == r2.Epoch && r.Round == r2.Round 35 | } 36 | -------------------------------------------------------------------------------- /offchainreporting/internal/protocol/telemetry.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "github.com/smartcontractkit/libocr/commontypes" 5 | "github.com/smartcontractkit/libocr/offchainreporting/types" 6 | ) 7 | 8 | type TelemetrySender interface { 9 | RoundStarted( 10 | configDigest types.ConfigDigest, 11 | epoch uint32, 12 | round uint8, 13 | leader commontypes.OracleID, 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /offchainreporting/internal/protocol/test_helpers.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import "github.com/smartcontractkit/libocr/commontypes" 4 | 5 | // Used only for testing 6 | type XXXUnknownMessageType struct{} 7 | 8 | // Conform to protocol.Message interface 9 | func (XXXUnknownMessageType) process(*oracleState, commontypes.OracleID) {} 10 | -------------------------------------------------------------------------------- /offchainreporting/internal/serialization/telemetry.go: -------------------------------------------------------------------------------- 1 | package serialization 2 | -------------------------------------------------------------------------------- /offchainreporting/internal/shim/telemetry_sender.go: -------------------------------------------------------------------------------- 1 | package shim 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/smartcontractkit/libocr/commontypes" 7 | "github.com/smartcontractkit/libocr/internal/loghelper" 8 | "github.com/smartcontractkit/libocr/offchainreporting/internal/serialization/protobuf" 9 | "github.com/smartcontractkit/libocr/offchainreporting/types" 10 | ) 11 | 12 | type TelemetrySender struct { 13 | chTelemetry chan<- *protobuf.TelemetryWrapper 14 | logger commontypes.Logger 15 | taper loghelper.LogarithmicTaper 16 | } 17 | 18 | func NewTelemetrySender(chTelemetry chan<- *protobuf.TelemetryWrapper, logger commontypes.Logger) *TelemetrySender { 19 | return &TelemetrySender{chTelemetry, logger, loghelper.LogarithmicTaper{}} 20 | } 21 | 22 | func (ts *TelemetrySender) send(t *protobuf.TelemetryWrapper) { 23 | select { 24 | case ts.chTelemetry <- t: 25 | ts.taper.Reset(func(oldCount uint64) { 26 | ts.logger.Info("TelemetrySender: stopped dropping telemetry", commontypes.LogFields{ 27 | "droppedCount": oldCount, 28 | }) 29 | }) 30 | default: 31 | ts.taper.Trigger(func(newCount uint64) { 32 | ts.logger.Warn("TelemetrySender: dropping telemetry", commontypes.LogFields{ 33 | "droppedCount": newCount, 34 | }) 35 | }) 36 | } 37 | } 38 | 39 | func (ts *TelemetrySender) RoundStarted( 40 | configDigest types.ConfigDigest, 41 | epoch uint32, 42 | round uint8, 43 | leader commontypes.OracleID, 44 | ) { 45 | t := time.Now().UnixNano() 46 | ts.send(&protobuf.TelemetryWrapper{ 47 | Wrapped: &protobuf.TelemetryWrapper_RoundStarted{&protobuf.TelemetryRoundStarted{ 48 | ConfigDigest: configDigest[:], 49 | Epoch: uint64(epoch), 50 | Round: uint64(round), 51 | Leader: uint64(leader), 52 | Time: uint64(t), 53 | }}, 54 | UnixTimeNanoseconds: t, 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /offchainreporting/internal/signature/off_chain_signature.go: -------------------------------------------------------------------------------- 1 | package signature 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ed25519" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | // OffchainPublicKey is the public key used to cryptographically identify an 11 | // oracle in inter-oracle communications. 12 | type OffchainPublicKey ed25519.PublicKey 13 | 14 | // Equal returns true iff k and k2 represent the same public key 15 | func (k OffchainPublicKey) Equal(k2 OffchainPublicKey) bool { 16 | return bytes.Equal([]byte(ed25519.PublicKey(k)), []byte(ed25519.PublicKey(k2))) 17 | } 18 | 19 | // Verify returns true iff signature is a valid signature by k on msg 20 | func (k OffchainPublicKey) Verify(msg, signature []byte) bool { 21 | return ed25519.Verify(ed25519.PublicKey(k), msg, signature) 22 | } 23 | 24 | // OffchainPrivateKey is the secret key oracles use to sign messages destined 25 | // for off-chain verification by other oracles 26 | type OffchainPrivateKey ed25519.PrivateKey 27 | 28 | // Sign returns the signature on msgHash with k 29 | func (k *OffchainPrivateKey) Sign(msg []byte) ([]byte, error) { 30 | if k == nil { 31 | return nil, errors.Errorf("attempt to sign with nil key") 32 | } 33 | return ed25519.Sign(ed25519.PrivateKey(*k), msg), nil 34 | } 35 | 36 | // PublicKey returns the public key which commits to k 37 | func (k *OffchainPrivateKey) PublicKey() OffchainPublicKey { 38 | return OffchainPublicKey(ed25519.PrivateKey(*k).Public().(ed25519.PublicKey)) 39 | } 40 | -------------------------------------------------------------------------------- /offchainreporting/internal/signature/on_chain_signature.go: -------------------------------------------------------------------------------- 1 | package signature 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ecdsa" 6 | "math" 7 | 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/ethereum/go-ethereum/crypto" 10 | "github.com/ethereum/go-ethereum/crypto/secp256k1" 11 | "github.com/pkg/errors" 12 | "github.com/smartcontractkit/libocr/commontypes" 13 | "github.com/smartcontractkit/libocr/offchainreporting/types" 14 | ) 15 | 16 | // Curve is the elliptic Curve on which on-chain messages are to be signed 17 | var Curve = secp256k1.S256() 18 | 19 | // OnChainPublicKey is the public key used to cryptographically identify an 20 | // oracle to the on-chain smart contract. 21 | type OnChainPublicKey ecdsa.PublicKey 22 | 23 | // Equal returns true iff k and k2 represent the same public key 24 | func (k OnChainPublicKey) Equal(k2 OnChainPublicKey) bool { 25 | return bytes.Equal( 26 | common.Address(k.Address()).Bytes(), 27 | common.Address(k2.Address()).Bytes(), 28 | ) 29 | } 30 | 31 | type EthAddresses = map[types.OnChainSigningAddress]commontypes.OracleID 32 | 33 | // VerifyOnChain returns an error unless signature is a valid signature by one 34 | // of the signers, in which case it returns the ID of the signer 35 | func VerifyOnChain(msg []byte, signature []byte, signers EthAddresses, 36 | ) (commontypes.OracleID, error) { 37 | author, err := crypto.SigToPub(onChainHash(msg), signature) 38 | if err != nil { 39 | return commontypes.OracleID(math.MaxUint8), errors.Wrapf(err, "while trying to recover "+ 40 | "sender from sig %x on msg %+v", signature, msg) 41 | } 42 | oid, ok := signers[(*OnChainPublicKey)(author).Address()] 43 | if ok { 44 | return oid, nil 45 | } else { 46 | return commontypes.OracleID(math.MaxUint8), errors.Errorf("signer is not on whitelist") 47 | } 48 | } 49 | 50 | // OnchainPrivateKey is the secret key oracles use to sign messages destined for 51 | // verification by the on-chain smart contract. 52 | type OnchainPrivateKey ecdsa.PrivateKey 53 | 54 | // Sign signs message with k 55 | func (k *OnchainPrivateKey) Sign(msg []byte) (signature []byte, err error) { 56 | sig, err := crypto.Sign(onChainHash(msg), (*ecdsa.PrivateKey)(k)) 57 | return sig, err 58 | } 59 | 60 | func onChainHash(msg []byte) []byte { 61 | return crypto.Keccak256(msg) 62 | } 63 | 64 | func (k OnChainPublicKey) Address() types.OnChainSigningAddress { 65 | return types.OnChainSigningAddress(crypto.PubkeyToAddress(ecdsa.PublicKey(k))) 66 | } 67 | 68 | func (k OnchainPrivateKey) Address() types.OnChainSigningAddress { 69 | return types.OnChainSigningAddress(crypto.PubkeyToAddress(k.PublicKey)) 70 | } 71 | -------------------------------------------------------------------------------- /offchainreporting/internal/test/doc.go: -------------------------------------------------------------------------------- 1 | package test 2 | -------------------------------------------------------------------------------- /offchainreporting/types/constants.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // The maximum number of oracles supported 4 | const MaxOracles = 31 5 | -------------------------------------------------------------------------------- /offchainreporting/types/db.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "context" 5 | "database/sql/driver" 6 | "time" 7 | 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | // Database persistently stores information on-disk. 12 | // All its functions should be thread-safe. 13 | type Database interface { 14 | ReadState(ctx context.Context, configDigest ConfigDigest) (*PersistentState, error) 15 | WriteState(ctx context.Context, configDigest ConfigDigest, state PersistentState) error 16 | 17 | ReadConfig(ctx context.Context) (*ContractConfig, error) 18 | WriteConfig(ctx context.Context, config ContractConfig) error 19 | 20 | StorePendingTransmission(context.Context, ReportTimestamp, PendingTransmission) error 21 | PendingTransmissionsWithConfigDigest(context.Context, ConfigDigest) (map[ReportTimestamp]PendingTransmission, error) 22 | DeletePendingTransmission(context.Context, ReportTimestamp) error 23 | DeletePendingTransmissionsOlderThan(context.Context, time.Time) error 24 | } 25 | 26 | type ReportTimestamp struct { 27 | ConfigDigest ConfigDigest 28 | Epoch uint32 29 | Round uint8 30 | } 31 | 32 | type PendingTransmission struct { 33 | Time time.Time 34 | Median Observation 35 | SerializedReport []byte 36 | Rs [][32]byte 37 | Ss [][32]byte 38 | Vs [32]byte 39 | } 40 | 41 | type PersistentState struct { 42 | Epoch uint32 43 | HighestSentEpoch uint32 44 | HighestReceivedEpoch []uint32 // length: at most MaxOracles 45 | } 46 | 47 | func (ps PersistentState) Equal(ps2 PersistentState) bool { 48 | if ps.Epoch != ps2.Epoch { 49 | return false 50 | } 51 | if ps.HighestSentEpoch != ps2.HighestSentEpoch { 52 | return false 53 | } 54 | if len(ps.HighestReceivedEpoch) != len(ps2.HighestReceivedEpoch) { 55 | return false 56 | } 57 | for i := 0; i < len(ps.HighestReceivedEpoch); i++ { 58 | if ps.HighestReceivedEpoch[i] != ps2.HighestReceivedEpoch[i] { 59 | return false 60 | } 61 | } 62 | return true 63 | } 64 | 65 | // 66 | // database/sql/driver interface functions for ConfigDigest 67 | // 68 | 69 | // Scan complies with sql Scanner interface 70 | func (c *ConfigDigest) Scan(value interface{}) error { 71 | b, ok := value.([]byte) 72 | if !ok { 73 | return errors.Errorf("unable to convert %v of type %T to ConfigDigest", value, value) 74 | } 75 | if len(b) != 16 { 76 | return errors.Errorf("unable to convert blob 0x%x of length %v to ConfigDigest", b, len(b)) 77 | } 78 | copy(c[:], b) 79 | return nil 80 | } 81 | 82 | // Value returns this instance serialized for database storage. 83 | func (c ConfigDigest) Value() (driver.Value, error) { 84 | return c[:], nil 85 | } 86 | -------------------------------------------------------------------------------- /offchainreporting/validate_local_config.go: -------------------------------------------------------------------------------- 1 | package offchainreporting 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/smartcontractkit/libocr/offchainreporting/types" 9 | ) 10 | 11 | func boundTimeDuration( 12 | duration time.Duration, 13 | durationName string, 14 | min time.Duration, 15 | max time.Duration, 16 | ) error { 17 | if !(min <= duration && duration <= max) { 18 | return fmt.Errorf("%s must be between %v and %v, but is currently %v", 19 | durationName, min, max, duration) 20 | } 21 | return nil 22 | } 23 | 24 | func SanityCheckLocalConfig(c types.LocalConfig) (err error) { 25 | if c.DevelopmentMode == types.EnableDangerousDevelopmentMode { 26 | return nil 27 | } 28 | 29 | err = errors.Join(err, 30 | boundTimeDuration( 31 | c.BlockchainTimeout, 32 | "blockchain timeout", 33 | 1*time.Second, 20*time.Second, 34 | )) 35 | err = errors.Join(err, 36 | boundTimeDuration( 37 | c.ContractConfigTrackerPollInterval, 38 | "contract config tracker poll interval", 39 | 15*time.Second, 120*time.Second, 40 | )) 41 | err = errors.Join(err, 42 | boundTimeDuration( 43 | c.ContractConfigTrackerSubscribeInterval, 44 | "contract config tracker subscribe interval", 45 | 1*time.Minute, 5*time.Minute, 46 | )) 47 | err = errors.Join(err, 48 | boundTimeDuration( 49 | c.ContractTransmitterTransmitTimeout, 50 | "contract transmitter transmit timeout", 51 | 1*time.Second, 1*time.Minute, 52 | )) 53 | err = errors.Join(err, 54 | boundTimeDuration( 55 | c.DatabaseTimeout, 56 | "database timeout", 57 | 100*time.Millisecond, 10*time.Second, 58 | )) 59 | err = errors.Join(err, 60 | boundTimeDuration( 61 | c.DataSourceTimeout, 62 | "data source timeout", 63 | 1*time.Second, 20*time.Second, 64 | )) 65 | err = errors.Join(err, 66 | boundTimeDuration( 67 | c.DataSourceGracePeriod, 68 | "data source grace period", 69 | 10*time.Millisecond, 2*time.Second, 70 | )) 71 | 72 | if c.DataSourceTimeout < c.DataSourceGracePeriod { 73 | err = errors.Join(err, fmt.Errorf( 74 | "data source timeout %v must be greater than data source grace period %v", 75 | c.DataSourceTimeout, 76 | c.DataSourceTimeout)) 77 | } 78 | 79 | const minContractConfigConfirmations = 1 80 | const maxContractConfigConfirmations = 100 81 | if !(minContractConfigConfirmations <= c.ContractConfigConfirmations && c.ContractConfigConfirmations <= maxContractConfigConfirmations) { 82 | err = errors.Join(err, fmt.Errorf( 83 | "contract config block-depth confirmation threshold must be between %v and %v, but is currently %v", 84 | minContractConfigConfirmations, 85 | maxContractConfigConfirmations, 86 | c.ContractConfigConfirmations)) 87 | 88 | } 89 | 90 | return err 91 | } 92 | -------------------------------------------------------------------------------- /offchainreporting2/chains/evmutil/alias.go: -------------------------------------------------------------------------------- 1 | // alias for offchainreporting2plus/chains/evmutil 2 | package evmutil 3 | 4 | import ( 5 | "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" 6 | "github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil" 7 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 8 | ) 9 | 10 | func SplitSignature(sig []byte) (r, s [32]byte, v byte, err error) { 11 | return evmutil.SplitSignature(sig) 12 | } 13 | 14 | func RawReportContext(repctx types.ReportContext) [3][32]byte { 15 | return evmutil.RawReportContext(repctx) 16 | } 17 | 18 | func ContractConfigFromConfigSetEvent(changed ocr2aggregator.OCR2AggregatorConfigSet) types.ContractConfig { 19 | return evmutil.ContractConfigFromConfigSetEvent(changed) 20 | } 21 | 22 | type EVMOffchainConfigDigester = evmutil.EVMOffchainConfigDigester 23 | -------------------------------------------------------------------------------- /offchainreporting2/reportingplugin/median/epochround.go: -------------------------------------------------------------------------------- 1 | package median 2 | 3 | type epochRound struct { 4 | Epoch uint32 5 | Round uint8 6 | } 7 | 8 | func (x epochRound) Less(y epochRound) bool { 9 | return x.Epoch < y.Epoch || (x.Epoch == y.Epoch && x.Round < y.Round) 10 | } 11 | -------------------------------------------------------------------------------- /offchainreporting2/reportingplugin/median/value.go: -------------------------------------------------------------------------------- 1 | package median 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/smartcontractkit/libocr/bigbigendian" 7 | ) 8 | 9 | var i = big.NewInt 10 | 11 | // Bounds on an int192 12 | const byteWidth = 24 13 | const bitWidth = byteWidth * 8 14 | 15 | var one *big.Int = big.NewInt(1) 16 | 17 | // 2**191-1 18 | func MaxValue() *big.Int { 19 | result := MinValue() 20 | result.Abs(result) 21 | result.Sub(result, one) 22 | return result 23 | } 24 | 25 | // -2**191 26 | func MinValue() *big.Int { 27 | result := &big.Int{} 28 | result.Lsh(one, bitWidth-1) 29 | result.Neg(result) 30 | return result 31 | } 32 | 33 | // Encodes a value using 24-byte big endian two's complement representation. This function never panics. 34 | func EncodeValue(i *big.Int) ([]byte, error) { 35 | return bigbigendian.SerializeSigned(byteWidth, i) 36 | } 37 | 38 | // Decodes a value using 24-byte big endian two's complement representation. This function never panics. 39 | func DecodeValue(s []byte) (*big.Int, error) { 40 | return bigbigendian.DeserializeSigned(byteWidth, s) 41 | } 42 | -------------------------------------------------------------------------------- /offchainreporting2/types/alias.go: -------------------------------------------------------------------------------- 1 | // alias for offchainreporting2plus/types 2 | package types 3 | 4 | import "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 5 | 6 | type ConfigDigestPrefix = types.ConfigDigestPrefix 7 | 8 | const ( 9 | // Deprecated: Use equivalent offchainreporting2plus/types.ConfigDigestPrefixEVMSimple instead 10 | ConfigDigestPrefixEVM ConfigDigestPrefix = types.ConfigDigestPrefixEVM //nolint:staticcheck 11 | ConfigDigestPrefixSolana ConfigDigestPrefix = types.ConfigDigestPrefixSolana 12 | ConfigDigestPrefixStarknet ConfigDigestPrefix = types.ConfigDigestPrefixStarknet 13 | ConfigDigestPrefixMercuryV02 ConfigDigestPrefix = types.ConfigDigestPrefixMercuryV02 14 | ConfigDigestPrefixOCR1 ConfigDigestPrefix = types.ConfigDigestPrefixOCR1 15 | ) 16 | 17 | type ConfigDigest = types.ConfigDigest 18 | 19 | func BytesToConfigDigest(b []byte) (ConfigDigest, error) { 20 | return types.BytesToConfigDigest(b) 21 | } 22 | 23 | type OffchainConfigDigester = types.OffchainConfigDigester 24 | 25 | const MaxOracles = types.MaxOracles 26 | 27 | type ConfigDatabase = types.ConfigDatabase 28 | 29 | type Database = types.Database 30 | 31 | type PendingTransmission = types.PendingTransmission 32 | 33 | type PersistentState = types.PersistentState 34 | 35 | const EnableDangerousDevelopmentMode = types.EnableDangerousDevelopmentMode 36 | 37 | type LocalConfig = types.LocalConfig 38 | 39 | type BinaryNetworkEndpointLimits = types.BinaryNetworkEndpointLimits 40 | 41 | type BinaryNetworkEndpointFactory = types.BinaryNetworkEndpointFactory 42 | 43 | type BootstrapperFactory = types.BootstrapperFactory 44 | 45 | type Query = types.Query 46 | 47 | type Observation = types.Observation 48 | 49 | type AttributedObservation = types.AttributedObservation 50 | 51 | type ReportTimestamp = types.ReportTimestamp 52 | 53 | type ReportContext = types.ReportContext 54 | 55 | type Report = types.Report 56 | 57 | type AttributedOnchainSignature = types.AttributedOnchainSignature 58 | 59 | type ReportingPluginFactory = types.ReportingPluginFactory 60 | 61 | type ReportingPluginConfig = types.ReportingPluginConfig 62 | 63 | type ReportingPlugin = types.ReportingPlugin 64 | 65 | const ( 66 | MaxMaxQueryLength = types.MaxMaxQueryLength 67 | MaxMaxObservationLength = types.MaxMaxObservationLength 68 | MaxMaxReportLength = types.MaxMaxReportLength 69 | ) 70 | 71 | type ReportingPluginLimits = types.ReportingPluginLimits 72 | 73 | type ReportingPluginInfo = types.ReportingPluginInfo 74 | 75 | type Account = types.Account 76 | 77 | type ContractTransmitter = types.ContractTransmitter 78 | 79 | type ContractConfigTracker = types.ContractConfigTracker 80 | 81 | type ContractConfig = types.ContractConfig 82 | 83 | type OffchainPublicKey = types.OffchainPublicKey 84 | 85 | type OnchainPublicKey = types.OnchainPublicKey 86 | 87 | type ConfigEncryptionPublicKey = types.ConfigEncryptionPublicKey 88 | 89 | type OffchainKeyring = types.OffchainKeyring 90 | 91 | type OnchainKeyring = types.OnchainKeyring 92 | -------------------------------------------------------------------------------- /offchainreporting2plus/chains/evmutil/config_digest.go: -------------------------------------------------------------------------------- 1 | package evmutil 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "strings" 7 | 8 | "github.com/ethereum/go-ethereum/accounts/abi" 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/crypto" 11 | 12 | "github.com/smartcontractkit/libocr/gethwrappers2/exposedocr2aggregator" 13 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 14 | ) 15 | 16 | func makeConfigDigestArgs() abi.Arguments { 17 | abi, err := abi.JSON(strings.NewReader( 18 | exposedocr2aggregator.ExposedOCR2AggregatorABI)) 19 | if err != nil { 20 | // assertion 21 | panic(fmt.Sprintf("could not parse aggregator ABI: %s", err.Error())) 22 | } 23 | return abi.Methods["exposedConfigDigestFromConfigData"].Inputs 24 | } 25 | 26 | var configDigestArgs = makeConfigDigestArgs() 27 | 28 | func configDigest( 29 | chainID uint64, 30 | contractAddress common.Address, 31 | configCount uint64, 32 | oracles []common.Address, 33 | transmitters []common.Address, 34 | f uint8, 35 | onchainConfig []byte, 36 | offchainConfigVersion uint64, 37 | offchainConfig []byte, 38 | ) types.ConfigDigest { 39 | chainIDBig := new(big.Int) 40 | chainIDBig.SetUint64(chainID) 41 | msg, err := configDigestArgs.Pack( 42 | chainIDBig, 43 | contractAddress, 44 | configCount, 45 | oracles, 46 | transmitters, 47 | f, 48 | onchainConfig, 49 | offchainConfigVersion, 50 | offchainConfig, 51 | ) 52 | if err != nil { 53 | // assertion 54 | panic(err) 55 | } 56 | rawHash := crypto.Keccak256(msg) 57 | configDigest := types.ConfigDigest{} 58 | if n := copy(configDigest[:], rawHash); n != len(configDigest) { 59 | // assertion 60 | panic("copy too little data") 61 | } 62 | if types.ConfigDigestPrefixEVMSimple != 1 { 63 | // assertion 64 | panic("wrong ConfigDigestPrefix") 65 | } 66 | configDigest[0] = 0 67 | configDigest[1] = 1 68 | return configDigest 69 | } 70 | -------------------------------------------------------------------------------- /offchainreporting2plus/chains/evmutil/evm.go: -------------------------------------------------------------------------------- 1 | package evmutil 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" 9 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 10 | ) 11 | 12 | func SplitSignature(sig []byte) (r, s [32]byte, v byte, err error) { 13 | if len(sig) != 65 { 14 | return r, s, v, fmt.Errorf("SplitSignature: wrong size") 15 | } 16 | r = common.BytesToHash(sig[:32]) 17 | s = common.BytesToHash(sig[32:64]) 18 | v = sig[64] 19 | return r, s, v, nil 20 | } 21 | 22 | func RawReportContext(repctx types.ReportContext) [3][32]byte { 23 | rawRepctx := [3][32]byte{} 24 | copy(rawRepctx[0][:], repctx.ConfigDigest[:]) 25 | binary.BigEndian.PutUint32(rawRepctx[1][32-5:32-1], repctx.Epoch) 26 | rawRepctx[1][31] = repctx.Round 27 | rawRepctx[2] = repctx.ExtraHash 28 | return rawRepctx 29 | } 30 | 31 | func ContractConfigFromConfigSetEvent(changed ocr2aggregator.OCR2AggregatorConfigSet) types.ContractConfig { 32 | transmitAccounts := []types.Account{} 33 | for _, addr := range changed.Transmitters { 34 | transmitAccounts = append(transmitAccounts, types.Account(addr.Hex())) 35 | } 36 | signers := []types.OnchainPublicKey{} 37 | for _, addr := range changed.Signers { 38 | signers = append(signers, types.OnchainPublicKey(addr[:])) 39 | } 40 | return types.ContractConfig{ 41 | changed.ConfigDigest, 42 | changed.ConfigCount, 43 | signers, 44 | transmitAccounts, 45 | changed.F, 46 | changed.OnchainConfig, 47 | changed.OffchainConfigVersion, 48 | changed.OffchainConfig, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /offchainreporting2plus/chains/evmutil/offchain_config_digester.go: -------------------------------------------------------------------------------- 1 | package evmutil 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 10 | ) 11 | 12 | var _ types.OffchainConfigDigester = EVMOffchainConfigDigester{} 13 | 14 | type EVMOffchainConfigDigester struct { 15 | ChainID uint64 16 | ContractAddress common.Address 17 | } 18 | 19 | func (d EVMOffchainConfigDigester) ConfigDigest(_ context.Context, cc types.ContractConfig) (types.ConfigDigest, error) { 20 | signers := []common.Address{} 21 | for i, signer := range cc.Signers { 22 | if len(signer) != 20 { 23 | return types.ConfigDigest{}, fmt.Errorf("%v-th evm signer should be a 20 byte address, but got %x", i, signer) 24 | } 25 | a := common.BytesToAddress(signer) 26 | signers = append(signers, a) 27 | } 28 | transmitters := []common.Address{} 29 | for i, transmitter := range cc.Transmitters { 30 | if !strings.HasPrefix(string(transmitter), "0x") || len(transmitter) != 42 || !common.IsHexAddress(string(transmitter)) { 31 | return types.ConfigDigest{}, fmt.Errorf("%v-th evm transmitter should be a 42 character Ethereum address string, but got '%v'", i, transmitter) 32 | } 33 | a := common.HexToAddress(string(transmitter)) 34 | transmitters = append(transmitters, a) 35 | } 36 | 37 | return configDigest( 38 | d.ChainID, 39 | d.ContractAddress, 40 | cc.ConfigCount, 41 | signers, 42 | transmitters, 43 | cc.F, 44 | cc.OnchainConfig, 45 | cc.OffchainConfigVersion, 46 | cc.OffchainConfig, 47 | ), nil 48 | } 49 | 50 | func (d EVMOffchainConfigDigester) ConfigDigestPrefix(context.Context) (types.ConfigDigestPrefix, error) { 51 | return types.ConfigDigestPrefixEVMSimple, nil 52 | } 53 | -------------------------------------------------------------------------------- /offchainreporting2plus/doc.go: -------------------------------------------------------------------------------- 1 | // Package offchainreporting2plus implements the Chainlink Offchain Reporting Protocol 2 | // 3 | // # A note about concurrency 4 | // 5 | // We spawn lots of goroutines in this package. As a general rule, we keep track 6 | // of all of them using the subprocesses package. We typically signal shutdowns 7 | // using contexts and then do a subprocesses.Wait() to ensure that we're not 8 | // leaking goroutines. 9 | 10 | package offchainreporting2plus 11 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/config/ethcontractconfig/ethereum_set_config_args.go: -------------------------------------------------------------------------------- 1 | package ethcontractconfig 2 | 3 | import "github.com/ethereum/go-ethereum/common" 4 | 5 | type SetConfigArgs struct { 6 | Signers []common.Address 7 | Transmitters []common.Address 8 | F uint8 9 | OnchainConfig []byte 10 | OffchainConfigVersion uint64 11 | OffchainConfig []byte 12 | } 13 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/config/netconfig/netconfig.go: -------------------------------------------------------------------------------- 1 | package netconfig 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config" 7 | "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr2config" 8 | "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config" 9 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 10 | ) 11 | 12 | type NetConfig struct { 13 | ConfigDigest types.ConfigDigest 14 | F int 15 | PeerIDs []string 16 | } 17 | 18 | func NetConfigFromContractConfig(contractConfig types.ContractConfig) (NetConfig, error) { 19 | switch contractConfig.OffchainConfigVersion { 20 | case config.OCR2OffchainConfigVersion: 21 | publicConfig, err := ocr2config.PublicConfigFromContractConfig(true, contractConfig) 22 | if err != nil { 23 | return NetConfig{}, err 24 | } 25 | return NetConfig{ 26 | publicConfig.ConfigDigest, 27 | publicConfig.F, 28 | peerIDs(publicConfig.OracleIdentities), 29 | }, nil 30 | case config.OCR3OffchainConfigVersion: 31 | publicConfig, err := ocr3config.PublicConfigFromContractConfig(true, contractConfig) 32 | if err != nil { 33 | return NetConfig{}, err 34 | } 35 | return NetConfig{ 36 | publicConfig.ConfigDigest, 37 | publicConfig.F, 38 | peerIDs(publicConfig.OracleIdentities), 39 | }, nil 40 | default: 41 | return NetConfig{}, fmt.Errorf("NetConfigFromContractConfig received OffchainConfigVersion %v", contractConfig.OffchainConfigVersion) 42 | } 43 | } 44 | 45 | func peerIDs(identities []config.OracleIdentity) []string { 46 | var peerIDs []string 47 | for _, identity := range identities { 48 | peerIDs = append(peerIDs, identity.PeerID) 49 | } 50 | return peerIDs 51 | } 52 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/config/offchain_config_versions.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | OCR2OffchainConfigVersion = 2 5 | OCR3OffchainConfigVersion = 30 6 | ) 7 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/config/oracle_identity.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 4 | 5 | type OracleIdentity struct { 6 | OffchainPublicKey types.OffchainPublicKey 7 | OnchainPublicKey types.OnchainPublicKey 8 | PeerID string 9 | TransmitAccount types.Account 10 | } 11 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/config/shared_secret_encrypt.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "crypto/aes" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/ethereum/go-ethereum/crypto" 10 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 11 | "golang.org/x/crypto/curve25519" 12 | ) 13 | 14 | // EncryptSharedSecretDeterministic constructs a SharedSecretEncryptions from 15 | // a set of ConfigEncryptionPublicKeys, the sharedSecret, and an 16 | // ephemeral secret key 17 | func EncryptSharedSecretDeterministic( 18 | publicKeys []types.ConfigEncryptionPublicKey, 19 | sharedSecret *[SharedSecretSize]byte, 20 | ephemeralSk *[curve25519.ScalarSize]byte, 21 | ) (SharedSecretEncryptions, error) { 22 | ephemeralPk, err := curve25519.X25519(ephemeralSk[:], curve25519.Basepoint) 23 | if err != nil { 24 | return SharedSecretEncryptions{}, fmt.Errorf("while computing ephemeral pk: %w", err) 25 | } 26 | 27 | var ephemeralPkArray [curve25519.PointSize]byte 28 | copy(ephemeralPkArray[:], ephemeralPk) 29 | 30 | encryptedSharedSecrets := []EncryptedSharedSecret{} 31 | for _, pk := range publicKeys { // encrypt sharedSecret with each pk 32 | pkBytes := [curve25519.PointSize]byte(pk) 33 | dhPoint, err := curve25519.X25519(ephemeralSk[:], pkBytes[:]) 34 | if err != nil { 35 | return SharedSecretEncryptions{}, fmt.Errorf("while computing dhPoint for %x: %w", pkBytes, err) 36 | } 37 | 38 | key := crypto.Keccak256(dhPoint)[:16] 39 | 40 | encryptedSharedSecret := EncryptedSharedSecret(aesEncryptBlock(key, sharedSecret[:])) 41 | encryptedSharedSecrets = append(encryptedSharedSecrets, encryptedSharedSecret) 42 | } 43 | 44 | return SharedSecretEncryptions{ 45 | ephemeralPkArray, 46 | common.BytesToHash(crypto.Keccak256(sharedSecret[:])), 47 | encryptedSharedSecrets, 48 | }, nil 49 | } 50 | 51 | // EncryptSharedSecret constructs a SharedSecretEncryptions from 52 | // a set of SharedSecretEncryptionPublicKeys, the sharedSecret, and a cryptographic 53 | // randomness source 54 | func EncryptSharedSecret( 55 | keys []types.ConfigEncryptionPublicKey, 56 | sharedSecret *[SharedSecretSize]byte, 57 | rand io.Reader, 58 | ) (SharedSecretEncryptions, error) { 59 | var sk [curve25519.ScalarSize]byte 60 | _, err := io.ReadFull(rand, sk[:]) 61 | if err != nil { 62 | return SharedSecretEncryptions{}, fmt.Errorf("could not read enough randomness for encryption: %w", err) 63 | } 64 | return EncryptSharedSecretDeterministic(keys, sharedSecret, &sk) 65 | } 66 | 67 | // Encrypt one block with AES-128 68 | func aesEncryptBlock(key, plaintext []byte) [16]byte { 69 | if len(key) != 16 { 70 | panic("key has wrong length") 71 | } 72 | if len(plaintext) != 16 { 73 | panic("ciphertext has wrong length") 74 | } 75 | 76 | cipher, err := aes.NewCipher(key) 77 | if err != nil { 78 | // assertion 79 | panic(fmt.Sprintf("Unexpected error during aes.NewCipher: %v", err)) 80 | } 81 | 82 | var ciphertext [16]byte 83 | cipher.Encrypt(ciphertext[:], plaintext) 84 | return ciphertext 85 | } 86 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/managed/collect_garbage.go: -------------------------------------------------------------------------------- 1 | package managed 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/smartcontractkit/libocr/commontypes" 9 | "github.com/smartcontractkit/libocr/internal/loghelper" 10 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 11 | ) 12 | 13 | const collectInterval = 10 * time.Minute 14 | const olderThan = 24 * time.Hour 15 | 16 | // collectGarbage periodically collects garbage left by old transmission protocol instances 17 | func collectGarbage( 18 | ctx context.Context, 19 | database types.Database, 20 | localConfig types.LocalConfig, 21 | logger loghelper.LoggerWithContext, 22 | ) { 23 | for { 24 | wait := collectInterval + time.Duration(rand.Float64()*5.0*60.0)*time.Second 25 | logger.Info("collectGarbage: going to sleep", commontypes.LogFields{ 26 | "duration": wait, 27 | }) 28 | select { 29 | case <-time.After(wait): 30 | logger.Info("collectGarbage: starting collection of old transmissions", commontypes.LogFields{ 31 | "olderThan": olderThan, 32 | }) 33 | // To make sure the context is not leaked we are wrapping the database query. 34 | func() { 35 | childCtx, childCancel := context.WithTimeout(ctx, localConfig.DatabaseTimeout) 36 | defer childCancel() 37 | err := database.DeletePendingTransmissionsOlderThan(childCtx, time.Now().Add(-olderThan)) 38 | if err != nil { 39 | logger.ErrorIfNotCanceled( 40 | "collectGarbage: error in DeletePendingTransmissionsOlderThan", 41 | childCtx, 42 | commontypes.LogFields{ 43 | "error": err, 44 | "olderThan": olderThan, 45 | }, 46 | ) 47 | } else { 48 | logger.Info("collectGarbage: finished collection", nil) 49 | } 50 | }() 51 | case <-ctx.Done(): 52 | logger.Info("collectGarbage: exiting", nil) 53 | return 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/managed/config_digest.go: -------------------------------------------------------------------------------- 1 | package managed 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 8 | ) 9 | 10 | // Convenience wrapper around OffchainConfigDigester 11 | type prefixCheckConfigDigester struct { 12 | offchainConfigDigester types.OffchainConfigDigester 13 | } 14 | 15 | // ConfigDigest method that checks that the computed ConfigDigest's prefix is 16 | // consistent with OffchainConfigDigester.ConfigDigestPrefix 17 | func (d prefixCheckConfigDigester) ConfigDigest(ctx context.Context, cc types.ContractConfig) (types.ConfigDigest, error) { 18 | prefix, err := d.offchainConfigDigester.ConfigDigestPrefix(ctx) 19 | if err != nil { 20 | return types.ConfigDigest{}, err 21 | } 22 | 23 | cd, err := d.offchainConfigDigester.ConfigDigest(ctx, cc) 24 | if err != nil { 25 | return types.ConfigDigest{}, err 26 | } 27 | 28 | if !prefix.IsPrefixOf(cd) { 29 | return types.ConfigDigest{}, fmt.Errorf("ConfigDigest has prefix %s, but wanted prefix %s", types.ConfigDigestPrefixFromConfigDigest(cd), prefix) 30 | } 31 | 32 | return cd, nil 33 | } 34 | 35 | // Check that the ContractConfig's ConfigDigest matches the one computed 36 | // offchain 37 | func (d prefixCheckConfigDigester) CheckContractConfig(ctx context.Context, cc types.ContractConfig) error { 38 | goodConfigDigest, err := d.ConfigDigest(ctx, cc) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | if goodConfigDigest != cc.ConfigDigest { 44 | return fmt.Errorf("ConfigDigest mismatch. Expected %s but got %s", goodConfigDigest, cc.ConfigDigest) 45 | } 46 | 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/managed/doc.go: -------------------------------------------------------------------------------- 1 | // Package managed provides "managed" versions of Oracle and Bootstrapper 2 | // that perform garbage collection, track on-chain configuration changes, 3 | // serializes messages to binary, etc... 4 | package managed 5 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/managed/forward_telemetry.go: -------------------------------------------------------------------------------- 1 | package managed 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/smartcontractkit/libocr/commontypes" 7 | "github.com/smartcontractkit/libocr/internal/loghelper" 8 | "google.golang.org/protobuf/proto" 9 | ) 10 | 11 | // forwardTelemetry receives monitoring events on chTelemetry, serializes them, and forwards 12 | // them to monitoringEndpoint 13 | func forwardTelemetry[M proto.Message]( 14 | ctx context.Context, 15 | 16 | logger loghelper.LoggerWithContext, 17 | monitoringEndpoint commontypes.MonitoringEndpoint, 18 | 19 | chTelemetry <-chan M, 20 | ) { 21 | for { 22 | select { 23 | case t, ok := <-chTelemetry: 24 | if !ok { 25 | // This isn't supposed to happen, but we still handle this case gracefully, 26 | // just in case... 27 | logger.Error("forwardTelemetry: chTelemetry closed unexpectedly. exiting", nil) 28 | return 29 | } 30 | bin, err := proto.Marshal(t) 31 | if err != nil { 32 | logger.Error("forwardTelemetry: failed to Marshal protobuf", commontypes.LogFields{ 33 | "proto": t, 34 | "error": err, 35 | }) 36 | break 37 | } 38 | if monitoringEndpoint != nil { 39 | monitoringEndpoint.SendLog(bin) 40 | } 41 | case <-ctx.Done(): 42 | logger.Info("forwardTelemetry: exiting", nil) 43 | return 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/managed/limits/util.go: -------------------------------------------------------------------------------- 1 | package limits 2 | 3 | import "sort" 4 | 5 | func max(x int, xs ...int) int { 6 | sort.Ints(xs) 7 | if len(xs) == 0 || xs[len(xs)-1] < x { 8 | return x 9 | } else { 10 | return xs[len(xs)-1] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/managed/load_from_database.go: -------------------------------------------------------------------------------- 1 | package managed 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/smartcontractkit/libocr/commontypes" 7 | "github.com/smartcontractkit/libocr/internal/loghelper" 8 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 9 | ) 10 | 11 | func loadConfigFromDatabase(ctx context.Context, database types.ConfigDatabase, logger loghelper.LoggerWithContext) *types.ContractConfig { 12 | cc, err := database.ReadConfig(ctx) 13 | if err != nil { 14 | logger.ErrorIfNotCanceled("loadConfigFromDatabase: Error during Database.ReadConfig", ctx, commontypes.LogFields{ 15 | "error": err, 16 | }) 17 | return nil 18 | } 19 | 20 | if cc == nil { 21 | logger.Info("loadConfigFromDatabase: Database.ReadConfig returned nil, no configuration to restore", nil) 22 | return nil 23 | } 24 | 25 | return cc 26 | } 27 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/managed/managed_bootstrapper.go: -------------------------------------------------------------------------------- 1 | package managed 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/smartcontractkit/libocr/commontypes" 8 | "github.com/smartcontractkit/libocr/internal/loghelper" 9 | "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/netconfig" 10 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 11 | ) 12 | 13 | // RunManagedBootstrapper runs a "managed" bootstrapper. It handles 14 | // configuration updates on the contract. 15 | func RunManagedBootstrapper( 16 | ctx context.Context, 17 | 18 | bootstrapperFactory types.BootstrapperFactory, 19 | v2bootstrappers []commontypes.BootstrapperLocator, 20 | contractConfigTracker types.ContractConfigTracker, 21 | database types.ConfigDatabase, 22 | localConfig types.LocalConfig, 23 | logger loghelper.LoggerWithContext, 24 | offchainConfigDigester types.OffchainConfigDigester, 25 | ) { 26 | runWithContractConfig( 27 | ctx, 28 | 29 | contractConfigTracker, 30 | database, 31 | func(ctx context.Context, logger loghelper.LoggerWithContext, contractConfig types.ContractConfig) (err error, retry bool) { 32 | config, err := netconfig.NetConfigFromContractConfig(contractConfig) 33 | if err != nil { 34 | return fmt.Errorf("ManagedBootstrapper: error while decoding ContractConfig: %w", err), false 35 | } 36 | 37 | bootstrapper, err := bootstrapperFactory.NewBootstrapper(config.ConfigDigest, config.PeerIDs, v2bootstrappers, config.F) 38 | if err != nil { 39 | logger.Error("ManagedBootstrapper: error during NewBootstrapper", commontypes.LogFields{ 40 | "error": err, 41 | "peerIDs": config.PeerIDs, 42 | "v2bootstrappers": v2bootstrappers, 43 | }) 44 | return fmt.Errorf("ManagedBootstrapper: error during NewBootstrapper: %w", err), true 45 | } 46 | 47 | if err := bootstrapper.Start(); err != nil { 48 | return fmt.Errorf("ManagedBootstrapper: error during bootstrapper.Start(): %w", err), true 49 | } 50 | defer loghelper.CloseLogError( 51 | bootstrapper, 52 | logger, 53 | "ManagedBootstrapper: error during bootstrapper.Close()", 54 | ) 55 | 56 | <-ctx.Done() 57 | 58 | return nil, false 59 | }, 60 | localConfig, 61 | logger, 62 | offchainConfigDigester, 63 | defaultRetryParams(), 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr2/protocol/attested_report.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/smartcontractkit/libocr/commontypes" 8 | "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config" 9 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 10 | ) 11 | 12 | type AttributedObservation struct { 13 | Observation types.Observation 14 | Observer commontypes.OracleID 15 | } 16 | 17 | type AttestedReportOne struct { 18 | Skip bool 19 | Report types.Report 20 | Signature []byte 21 | } 22 | 23 | func MakeAttestedReportOneSkip() AttestedReportOne { 24 | return AttestedReportOne{true, nil, nil} 25 | } 26 | 27 | func MakeAttestedReportOneNoskip( 28 | repctx types.ReportContext, 29 | report types.Report, 30 | signer func(types.ReportContext, types.Report) ([]byte, error), 31 | ) (AttestedReportOne, error) { 32 | sig, err := signer(repctx, report) 33 | if err != nil { 34 | return AttestedReportOne{}, fmt.Errorf("error while signing in MakeAttestedReportOneNoskip: %w", err) 35 | } 36 | 37 | return AttestedReportOne{false, report, sig}, nil 38 | } 39 | 40 | func (rep AttestedReportOne) EqualExceptSignature(rep2 AttestedReportOne) bool { 41 | return rep.Skip == rep2.Skip && bytes.Equal(rep.Report, rep2.Report) 42 | } 43 | 44 | // Verify is used by the leader to check the signature a process attaches to its 45 | // report message (the c.Sig value.) 46 | func (aro *AttestedReportOne) Verify(contractSigner types.OnchainKeyring, publicKey types.OnchainPublicKey, repctx types.ReportContext) (err error) { 47 | if aro.Skip { 48 | if len(aro.Report) != 0 || len(aro.Signature) != 0 { 49 | return fmt.Errorf("AttestedReportOne with Skip=true has non-empty Report or Signature") 50 | } 51 | } else { 52 | ok := contractSigner.Verify(publicKey, repctx, aro.Report, aro.Signature) 53 | if !ok { 54 | return fmt.Errorf("failed to verify signature on AttestedReportOne") 55 | } 56 | } 57 | return nil 58 | } 59 | 60 | type AttestedReportMany struct { 61 | Report types.Report 62 | AttributedSignatures []types.AttributedOnchainSignature 63 | } 64 | 65 | func (rep *AttestedReportMany) VerifySignatures( 66 | numSignatures int, 67 | onchainKeyring types.OnchainKeyring, 68 | oracleIdentities []config.OracleIdentity, 69 | repctx types.ReportContext, 70 | ) error { 71 | if numSignatures != len(rep.AttributedSignatures) { 72 | return fmt.Errorf("wrong number of signatures, expected %v and got %v", numSignatures, len(rep.AttributedSignatures)) 73 | } 74 | seen := make(map[commontypes.OracleID]bool) 75 | for i, sig := range rep.AttributedSignatures { 76 | if seen[sig.Signer] { 77 | return fmt.Errorf("duplicate Signature by %v", sig.Signer) 78 | } 79 | seen[sig.Signer] = true 80 | if !(0 <= int(sig.Signer) && int(sig.Signer) < len(oracleIdentities)) { 81 | return fmt.Errorf("signer out of bounds: %v", sig.Signer) 82 | } 83 | if !onchainKeyring.Verify(oracleIdentities[sig.Signer].OnchainPublicKey, repctx, rep.Report, sig.Signature) { 84 | return fmt.Errorf("%v-th signature by %v-th oracle with pubkey %x does not verify", i, sig.Signer, oracleIdentities[sig.Signer].OnchainPublicKey) 85 | } 86 | } 87 | return nil 88 | } 89 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr2/protocol/attested_report_test_equal.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | // The functions in this file are only used in tests, hence 8 | // the name "TestEqual" to make that more clear 9 | 10 | func (rep AttestedReportOne) TestEqual(rep2 AttestedReportOne) bool { 11 | return rep.Skip == rep2.Skip && bytes.Equal(rep.Report, rep2.Report) && bytes.Equal(rep.Signature, rep2.Signature) 12 | } 13 | 14 | func (rep AttestedReportMany) TestEqual(c2 AttestedReportMany) bool { 15 | if !bytes.Equal(rep.Report, c2.Report) { 16 | return false 17 | } 18 | 19 | if len(rep.AttributedSignatures) != len(c2.AttributedSignatures) { 20 | return false 21 | } 22 | 23 | for i := range rep.AttributedSignatures { 24 | if !rep.AttributedSignatures[i].Equal(c2.AttributedSignatures[i]) { 25 | return false 26 | } 27 | } 28 | 29 | return true 30 | } 31 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr2/protocol/common.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import "time" 4 | 5 | type EpochRound struct { 6 | Epoch uint32 7 | Round uint8 8 | } 9 | 10 | func (x EpochRound) Less(y EpochRound) bool { 11 | return x.Epoch < y.Epoch || (x.Epoch == y.Epoch && x.Round < y.Round) 12 | } 13 | 14 | const ReportingPluginTimeoutWarningGracePeriod = 100 * time.Millisecond 15 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr2/protocol/heap.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "container/heap" 5 | 6 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 7 | ) 8 | 9 | // Type safe wrapper around MinHeapTimeToContractReportInternal 10 | type MinHeapTimeToPendingTransmission struct { 11 | internal MinHeapTimeToPendingTransmissionInternal 12 | } 13 | 14 | func (h *MinHeapTimeToPendingTransmission) Push(item MinHeapTimeToPendingTransmissionItem) { 15 | heap.Push(&h.internal, item) 16 | } 17 | 18 | func (h *MinHeapTimeToPendingTransmission) Pop() MinHeapTimeToPendingTransmissionItem { 19 | return heap.Pop(&h.internal).(MinHeapTimeToPendingTransmissionItem) 20 | } 21 | 22 | func (h *MinHeapTimeToPendingTransmission) Peek() MinHeapTimeToPendingTransmissionItem { 23 | return h.internal[0] 24 | } 25 | 26 | func (h *MinHeapTimeToPendingTransmission) Len() int { 27 | return h.internal.Len() 28 | } 29 | 30 | type MinHeapTimeToPendingTransmissionItem struct { 31 | types.ReportTimestamp 32 | types.PendingTransmission 33 | } 34 | 35 | // Implements heap.Interface and uses interface{} all over the place. 36 | type MinHeapTimeToPendingTransmissionInternal []MinHeapTimeToPendingTransmissionItem 37 | 38 | func (pq MinHeapTimeToPendingTransmissionInternal) Len() int { return len(pq) } 39 | 40 | func (pq MinHeapTimeToPendingTransmissionInternal) Less(i, j int) bool { 41 | return pq[i].Time.Before(pq[j].Time) 42 | } 43 | 44 | func (pq MinHeapTimeToPendingTransmissionInternal) Swap(i, j int) { 45 | pq[i], pq[j] = pq[j], pq[i] 46 | } 47 | 48 | func (pq *MinHeapTimeToPendingTransmissionInternal) Push(x interface{}) { 49 | item := x.(MinHeapTimeToPendingTransmissionItem) 50 | *pq = append(*pq, item) 51 | } 52 | 53 | func (pq *MinHeapTimeToPendingTransmissionInternal) Pop() interface{} { 54 | old := *pq 55 | n := len(old) 56 | item := old[n-1] 57 | *pq = old[0 : n-1] 58 | return item 59 | } 60 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr2/protocol/message_test_equal.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import "bytes" 4 | 5 | // The functions in this file are only used in tests, hence 6 | // the name "TestEqual" to make that more clear 7 | 8 | func (msg MessageObserveReq) TestEqual(msg2 MessageObserveReq) bool { 9 | return msg.Epoch == msg2.Epoch && 10 | msg.Round == msg2.Round && 11 | bytes.Equal(msg.Query, msg2.Query) 12 | } 13 | 14 | func (msg MessageObserve) TestEqual(msg2 MessageObserve) bool { 15 | return msg.Epoch == msg2.Epoch && 16 | msg.Round == msg2.Round && 17 | msg.SignedObservation.Equal(msg2.SignedObservation) 18 | } 19 | 20 | func (msg MessageReportReq) TestEqual(msg2 MessageReportReq) bool { 21 | if !(msg.Epoch == msg2.Epoch && 22 | msg.Round == msg2.Round && 23 | bytes.Equal(msg.Query, msg2.Query)) { 24 | return false 25 | } 26 | if len(msg.AttributedSignedObservations) != len(msg2.AttributedSignedObservations) { 27 | return false 28 | } 29 | for i := range msg.AttributedSignedObservations { 30 | if !msg.AttributedSignedObservations[i].Equal(msg2.AttributedSignedObservations[i]) { 31 | return false 32 | } 33 | } 34 | return true 35 | } 36 | 37 | func (msg MessageReport) TestEqual(m2 MessageReport) bool { 38 | return msg.Epoch == m2.Epoch && msg.Round == m2.Round && msg.AttestedReport.TestEqual(m2.AttestedReport) 39 | } 40 | 41 | func (msg MessageFinal) TestEqual(m2 MessageFinal) bool { 42 | return msg.Epoch == m2.Epoch && msg.Round == m2.Round && msg.AttestedReport.TestEqual(m2.AttestedReport) 43 | } 44 | 45 | func (msg MessageFinalEcho) TestEqual(m2 MessageFinalEcho) bool { 46 | return msg.MessageFinal.TestEqual(m2.MessageFinal) 47 | } 48 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr2/protocol/messagebuffer.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | // MessageBuffer implements a fixed capacity ringbuffer for items of type 4 | // MessageToReportGeneration 5 | type MessageBuffer struct { 6 | start int 7 | length int 8 | buffer []*MessageToReportGeneration 9 | } 10 | 11 | func NewMessageBuffer(cap int) *MessageBuffer { 12 | return &MessageBuffer{ 13 | 0, 14 | 0, 15 | make([]*MessageToReportGeneration, cap), 16 | } 17 | } 18 | 19 | // Peek at the front item 20 | func (rb *MessageBuffer) Peek() *MessageToReportGeneration { 21 | if rb.length == 0 { 22 | return nil 23 | } else { 24 | return rb.buffer[rb.start] 25 | } 26 | } 27 | 28 | // Pop front item 29 | func (rb *MessageBuffer) Pop() *MessageToReportGeneration { 30 | result := rb.Peek() 31 | if result != nil { 32 | rb.buffer[rb.start] = nil 33 | rb.start = (rb.start + 1) % len(rb.buffer) 34 | rb.length-- 35 | } 36 | return result 37 | } 38 | 39 | // Push new item to back. If the additional item would lead 40 | // to the capacity being exceeded, remove the front item first 41 | func (rb *MessageBuffer) Push(msg MessageToReportGeneration) { 42 | if rb.length == len(rb.buffer) { 43 | rb.Pop() 44 | } 45 | rb.buffer[(rb.start+rb.length)%len(rb.buffer)] = &msg 46 | rb.length++ 47 | } 48 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr2/protocol/network.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/smartcontractkit/libocr/commontypes" 7 | ) 8 | 9 | // NetworkSender sends messages to other oracles 10 | type NetworkSender interface { 11 | // SendTo(msg, to) sends msg to "to" 12 | SendTo(msg Message, to commontypes.OracleID) 13 | // Broadcast(msg) sends msg to all oracles 14 | Broadcast(msg Message) 15 | } 16 | 17 | // NetworkEndpoint sends & receives messages to/from other oracles 18 | type NetworkEndpoint interface { 19 | NetworkSender 20 | // Receive returns channel which carries all messages sent to this oracle 21 | Receive() <-chan MessageWithSender 22 | 23 | // Start must be called before Receive. Calling Start more than once causes 24 | // panic. 25 | Start() error 26 | 27 | // Close must be called before receive. Close can be called multiple times. 28 | // Close can be called even on an unstarted NetworkEndpoint. 29 | Close() error 30 | } 31 | 32 | // SimpleNetwork is a strawman (in-memory) implementation of the Network 33 | // interface. Network channels are buffered and can queue up to 100 messages 34 | // before blocking. 35 | type SimpleNetwork struct { 36 | chs []chan MessageWithSender // i'th channel models oracle i's network 37 | } 38 | 39 | // NewSimpleNetwork returns a SimpleNetwork for n oracles 40 | func NewSimpleNetwork(n int) *SimpleNetwork { 41 | s := SimpleNetwork{} 42 | for i := 0; i < n; i++ { 43 | s.chs = append(s.chs, make(chan MessageWithSender, 100)) 44 | } 45 | return &s 46 | } 47 | 48 | // Endpoint returns the interface for oracle id's networking facilities 49 | func (net *SimpleNetwork) Endpoint(id commontypes.OracleID) (NetworkEndpoint, error) { 50 | return SimpleNetworkEndpoint{ 51 | net, 52 | id, 53 | }, nil 54 | } 55 | 56 | // SimpleNetworkEndpoint is a strawman (in-memory) implementation of 57 | // NetworkEndpoint, used by SimpleNetwork 58 | type SimpleNetworkEndpoint struct { 59 | net *SimpleNetwork // Reference back to network for all participants 60 | id commontypes.OracleID // Index of oracle this endpoint pertains to 61 | } 62 | 63 | var _ NetworkEndpoint = (*SimpleNetworkEndpoint)(nil) 64 | 65 | // SendTo sends msg to oracle "to" 66 | func (end SimpleNetworkEndpoint) SendTo(msg Message, to commontypes.OracleID) { 67 | log.Printf("[%v] sending to %v: %T\n", end.id, to, msg) 68 | end.net.chs[to] <- MessageWithSender{msg, end.id} 69 | } 70 | 71 | // Broadcast sends msg to all participating oracles 72 | func (end SimpleNetworkEndpoint) Broadcast(msg Message) { 73 | log.Printf("[%v] broadcasting: %T\n", end.id, msg) 74 | for _, ch := range end.net.chs { 75 | ch <- MessageWithSender{msg, end.id} 76 | } 77 | } 78 | 79 | // Receive returns a channel which carries all messages sent to the oracle 80 | func (end SimpleNetworkEndpoint) Receive() <-chan MessageWithSender { 81 | return end.net.chs[end.id] 82 | } 83 | 84 | // Start satisfies the interface 85 | func (SimpleNetworkEndpoint) Start() error { return nil } 86 | 87 | // Close satisfies the interface 88 | func (SimpleNetworkEndpoint) Close() error { return nil } 89 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr2/protocol/observation.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ed25519" 6 | "crypto/sha256" 7 | "encoding/binary" 8 | "fmt" 9 | 10 | "github.com/smartcontractkit/libocr/commontypes" 11 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 12 | ) 13 | 14 | type SignedObservation struct { 15 | Observation types.Observation 16 | Signature []byte 17 | } 18 | 19 | func MakeSignedObservation( 20 | repts types.ReportTimestamp, 21 | query types.Query, 22 | observation types.Observation, 23 | signer func(msg []byte) (sig []byte, err error), 24 | ) ( 25 | SignedObservation, 26 | error, 27 | ) { 28 | payload := signedObservationWireMessage(repts, query, observation) 29 | sig, err := signer(payload) 30 | if err != nil { 31 | return SignedObservation{}, err 32 | } 33 | return SignedObservation{observation, sig}, nil 34 | } 35 | 36 | func (so SignedObservation) Equal(so2 SignedObservation) bool { 37 | return bytes.Equal(so.Observation, so2.Observation) && 38 | bytes.Equal(so.Signature, so2.Signature) 39 | } 40 | 41 | func (so SignedObservation) Verify(repts types.ReportTimestamp, query types.Query, publicKey types.OffchainPublicKey) error { 42 | pk := ed25519.PublicKey(publicKey[:]) 43 | // should never trigger since types.OffchainPublicKey is an array with length ed25519.PublicKeySize 44 | if len(pk) != ed25519.PublicKeySize { 45 | return fmt.Errorf("ed25519 public key size mismatch, expected %v but got %v", ed25519.PublicKeySize, len(pk)) 46 | } 47 | 48 | ok := ed25519.Verify(pk, signedObservationWireMessage(repts, query, so.Observation), so.Signature) 49 | if !ok { 50 | return fmt.Errorf("SignedObservation has invalid signature") 51 | } 52 | 53 | return nil 54 | } 55 | 56 | func signedObservationWireMessage(repts types.ReportTimestamp, query types.Query, observation types.Observation) []byte { 57 | h := sha256.New() 58 | // ConfigDigest 59 | _, _ = h.Write(repts.ConfigDigest[:]) 60 | _ = binary.Write(h, binary.BigEndian, repts.Epoch) 61 | _, _ = h.Write([]byte{repts.Round}) 62 | 63 | // Query 64 | _ = binary.Write(h, binary.BigEndian, uint64(len(query))) 65 | _, _ = h.Write(query) 66 | 67 | // Observation 68 | _ = binary.Write(h, binary.BigEndian, uint64(len(observation))) 69 | _, _ = h.Write(observation) 70 | 71 | return h.Sum(nil) 72 | } 73 | 74 | type AttributedSignedObservation struct { 75 | SignedObservation SignedObservation 76 | Observer commontypes.OracleID 77 | } 78 | 79 | func (aso AttributedSignedObservation) Equal(aso2 AttributedSignedObservation) bool { 80 | return aso.SignedObservation.Equal(aso2.SignedObservation) && 81 | aso.Observer == aso2.Observer 82 | } 83 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr2/protocol/persist/persist_pacemaker.go: -------------------------------------------------------------------------------- 1 | package persist 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/smartcontractkit/libocr/commontypes" 8 | "github.com/smartcontractkit/libocr/internal/loghelper" 9 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 10 | ) 11 | 12 | type persistPacemakerState struct { 13 | ctx context.Context 14 | 15 | chPersist <-chan types.PersistentState 16 | configDigest types.ConfigDigest 17 | database types.Database 18 | databaseTimeout time.Duration 19 | logger loghelper.LoggerWithContext 20 | 21 | writtenState *types.PersistentState 22 | } 23 | 24 | // PersistPacemaker receives states from the pacemaker protocol it should 25 | // persist to the db through chPersist and writes them to database. 26 | func PersistPacemaker( 27 | ctx context.Context, 28 | chPersist <-chan types.PersistentState, 29 | configDigest types.ConfigDigest, 30 | database types.Database, 31 | databaseTimeout time.Duration, 32 | logger loghelper.LoggerWithContext, 33 | ) { 34 | ps := persistPacemakerState{ 35 | ctx, 36 | 37 | chPersist, 38 | configDigest, 39 | database, 40 | databaseTimeout, 41 | logger, 42 | 43 | nil, 44 | } 45 | ps.run() 46 | } 47 | 48 | // run gets updates from the outside (through chPersist) in a loop, drains 49 | // chPersist so that it can ignore all but the latest state, and writes the 50 | // latest state to the database if it's new, i.e. differs from the previously 51 | // written state. 52 | func (ps *persistPacemakerState) run() { 53 | for { 54 | select { 55 | case state, ok := <-ps.chPersist: 56 | if !ok { 57 | ps.logger.Error("Persist: chPersist closed unexpectedly, can no longer persist state. This should *not* happen.", commontypes.LogFields{ 58 | "lastWrittenState": ps.writtenState, 59 | }) 60 | return 61 | } 62 | DrainChannel: 63 | for { 64 | select { 65 | case state, ok = <-ps.chPersist: 66 | if !ok { 67 | ps.logger.Error("Persist: chPersist closed unexpectedly, can no longer persist state. This should *not* happen.", commontypes.LogFields{ 68 | "lastWrittenState": ps.writtenState, 69 | }) 70 | return 71 | } 72 | default: 73 | break DrainChannel 74 | } 75 | } 76 | ps.writeIfNew(state) 77 | 78 | case <-ps.ctx.Done(): 79 | ps.logger.Debug("Persist: exiting", nil) 80 | return 81 | } 82 | } 83 | } 84 | 85 | // writeIfNew writes pendingState to the database, iff pendingState differs from 86 | // the last written state. 87 | func (ps *persistPacemakerState) writeIfNew(pendingState types.PersistentState) { 88 | if ps.writtenState != nil && pendingState.Equal(*ps.writtenState) { 89 | return 90 | } 91 | 92 | writeCtx, writeCancel := context.WithTimeout(ps.ctx, ps.databaseTimeout) 93 | defer writeCancel() 94 | err := ps.database.WriteState( 95 | writeCtx, 96 | ps.configDigest, 97 | pendingState, 98 | ) 99 | if err != nil { 100 | ps.logger.ErrorIfNotCanceled("Persist: unexpected error while persisting state to database", writeCtx, commontypes.LogFields{ 101 | "error": err, 102 | }) 103 | return 104 | } 105 | 106 | ps.writtenState = &pendingState 107 | } 108 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr2/protocol/persist/persist_transmission.go: -------------------------------------------------------------------------------- 1 | package persist 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/smartcontractkit/libocr/commontypes" 8 | "github.com/smartcontractkit/libocr/internal/loghelper" 9 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 10 | ) 11 | 12 | type TransmissionDBUpdate struct { 13 | Timestamp types.ReportTimestamp 14 | PendingTransmission *types.PendingTransmission 15 | } 16 | 17 | // Persists state from the transmission protocol to the database to allow for recovery 18 | // after restarts 19 | func PersistTransmission( 20 | ctx context.Context, 21 | chPersist <-chan TransmissionDBUpdate, 22 | db types.Database, 23 | dbTimeout time.Duration, 24 | logger loghelper.LoggerWithContext, 25 | ) { 26 | for { 27 | select { 28 | case update, ok := <-chPersist: 29 | if !ok { 30 | logger.Error("PersistTransmission: chPersist closed unexpectedly, exiting", nil) 31 | return 32 | } 33 | 34 | func() { 35 | dbCtx, dbCancel := context.WithTimeout(ctx, dbTimeout) 36 | defer dbCancel() 37 | 38 | store := update.PendingTransmission != nil 39 | var err error 40 | if store { 41 | err = db.StorePendingTransmission(dbCtx, update.Timestamp, *update.PendingTransmission) 42 | } else { 43 | err = db.DeletePendingTransmission(dbCtx, update.Timestamp) 44 | } 45 | if err != nil { 46 | logger.ErrorIfNotCanceled( 47 | "PersistTransmission: error updating database", 48 | dbCtx, 49 | commontypes.LogFields{"error": err, "store": store}, 50 | ) 51 | return 52 | } 53 | }() 54 | 55 | case <-ctx.Done(): 56 | logger.Info("PersistTransmission: exiting", nil) 57 | return 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr2/protocol/telemetry.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/smartcontractkit/libocr/commontypes" 7 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 8 | ) 9 | 10 | type TelemetrySender interface { 11 | EpochStarted( 12 | configDigest types.ConfigDigest, 13 | epoch uint32, 14 | leader commontypes.OracleID, 15 | ) 16 | 17 | RoundStarted( 18 | reportTimestamp types.ReportTimestamp, 19 | leader commontypes.OracleID, 20 | ) 21 | 22 | TransmissionScheduleComputed( 23 | reportTimestamp types.ReportTimestamp, 24 | now time.Time, 25 | schedule map[commontypes.OracleID]time.Duration, 26 | ) 27 | 28 | TransmissionShouldAcceptFinalizedReportComputed( 29 | reportTimestamp types.ReportTimestamp, 30 | result bool, 31 | ok bool, 32 | ) 33 | 34 | TransmissionShouldTransmitAcceptedReportComputed( 35 | reportTimestamp types.ReportTimestamp, 36 | result bool, 37 | ok bool, 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr2/protocol/test_helpers.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "github.com/smartcontractkit/libocr/commontypes" 5 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 6 | ) 7 | 8 | // Used only for testing 9 | type XXXUnknownMessageType struct{} 10 | 11 | // Conform to protocol.Message interface 12 | func (XXXUnknownMessageType) CheckSize(types.ReportingPluginLimits) bool { return true } 13 | func (XXXUnknownMessageType) process(*oracleState, commontypes.OracleID) {} 14 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr2/serialization/telemetry.go: -------------------------------------------------------------------------------- 1 | package serialization 2 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr3/minheap/minheap.go: -------------------------------------------------------------------------------- 1 | package minheap 2 | 3 | import ( 4 | "container/heap" 5 | ) 6 | 7 | // LessFn defines a less-than relation between a and b, i.e. a < b 8 | type LessFn[T any] func(a, b T) bool 9 | 10 | func NewMinHeap[T any](lessFn LessFn[T]) *MinHeap[T] { 11 | return &MinHeap[T]{minHeapInternal[T]{lessFn, nil}} 12 | } 13 | 14 | // Type-safe wrapper around minHeapInternal 15 | type MinHeap[T any] struct { 16 | internal minHeapInternal[T] 17 | } 18 | 19 | func (h *MinHeap[T]) Push(item T) { 20 | heap.Push(&h.internal, item) 21 | } 22 | 23 | func (h *MinHeap[T]) Pop() (T, bool) { 24 | if 0 < len(h.internal.items) { 25 | return heap.Pop(&h.internal).(T), true 26 | } else { 27 | var zero T 28 | return zero, false 29 | } 30 | } 31 | 32 | func (h *MinHeap[T]) Peek() (T, bool) { 33 | if 0 < len(h.internal.items) { 34 | return h.internal.items[0], true 35 | } else { 36 | var zero T 37 | return zero, false 38 | } 39 | } 40 | 41 | func (h *MinHeap[T]) Len() int { 42 | return h.internal.Len() 43 | } 44 | 45 | // Implements heap.Interface and uses interface{} all over the place. 46 | type minHeapInternal[T any] struct { 47 | lessFn LessFn[T] 48 | items []T 49 | } 50 | 51 | var _ heap.Interface = new(minHeapInternal[struct{}]) 52 | 53 | func (hi *minHeapInternal[T]) Len() int { return len(hi.items) } 54 | 55 | func (hi *minHeapInternal[T]) Less(i, j int) bool { 56 | return hi.lessFn(hi.items[i], hi.items[j]) 57 | } 58 | 59 | func (hi *minHeapInternal[T]) Swap(i, j int) { 60 | hi.items[i], hi.items[j] = hi.items[j], hi.items[i] 61 | } 62 | 63 | func (hi *minHeapInternal[T]) Push(x interface{}) { 64 | item := x.(T) 65 | hi.items = append(hi.items, item) 66 | } 67 | 68 | func (hi *minHeapInternal[T]) Pop() interface{} { 69 | old := hi.items 70 | n := len(old) 71 | item := old[n-1] 72 | hi.items = old[0 : n-1] 73 | return item 74 | } 75 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr3/protocol/attested_report.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" 5 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 6 | ) 7 | 8 | type AttestedReportMany[RI any] struct { 9 | ReportWithInfo ocr3types.ReportWithInfo[RI] 10 | AttributedSignatures []types.AttributedOnchainSignature 11 | } 12 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr3/protocol/common.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/smartcontractkit/libocr/commontypes" 9 | "github.com/smartcontractkit/libocr/internal/loghelper" 10 | ) 11 | 12 | const ReportingPluginTimeoutWarningGracePeriod = 100 * time.Millisecond 13 | 14 | func callPlugin[T any]( 15 | ctx context.Context, 16 | logger loghelper.LoggerWithContext, 17 | logFields commontypes.LogFields, 18 | name string, 19 | maxDuration time.Duration, 20 | f func(context.Context) (T, error), 21 | ) (T, bool) { 22 | pluginCtx, cancel := context.WithTimeout(ctx, maxDuration) 23 | defer cancel() 24 | 25 | ins := loghelper.NewIfNotStopped( 26 | maxDuration+ReportingPluginTimeoutWarningGracePeriod, 27 | func() { 28 | logger.MakeChild(logFields).Warn(fmt.Sprintf("call to ReportingPlugin.%s is taking too long", name), commontypes.LogFields{ 29 | "maxDuration": maxDuration.String(), 30 | "gracePeriod": ReportingPluginTimeoutWarningGracePeriod.String(), 31 | }) 32 | }, 33 | ) 34 | 35 | result, err := f(pluginCtx) 36 | 37 | ins.Stop() 38 | 39 | if err != nil { 40 | logger.MakeChild(logFields).ErrorIfNotCanceled(fmt.Sprintf("call to ReportingPlugin.%s errored", name), ctx, commontypes.LogFields{ 41 | "error": err, 42 | }) 43 | // failed to get data, nothing to be done 44 | var zero T 45 | return zero, false 46 | } 47 | 48 | return result, true 49 | } 50 | 51 | // Unlike callPlugin, callPluginFromBackground only uses the "recommendedMaxDuration" to warn 52 | // if the call takes longer than recommended, but does not use it for context expiration 53 | // purposes. Context expiration is solely controlled by the passed ctx. 54 | func callPluginFromBackground[T any]( 55 | ctx context.Context, 56 | logger loghelper.LoggerWithContext, 57 | logFields commontypes.LogFields, 58 | name string, 59 | recommendedMaxDuration time.Duration, 60 | f func(context.Context) (T, error), 61 | ) (T, bool) { 62 | ins := loghelper.NewIfNotStopped( 63 | recommendedMaxDuration+ReportingPluginTimeoutWarningGracePeriod, 64 | func() { 65 | logger.MakeChild(logFields).Warn(fmt.Sprintf("call to ReportingPlugin.%s is taking longer than recommended", name), commontypes.LogFields{ 66 | "recommendedMaxDuration": recommendedMaxDuration.String(), 67 | "gracePeriod": ReportingPluginTimeoutWarningGracePeriod.String(), 68 | }) 69 | }, 70 | ) 71 | 72 | result, err := f(ctx) 73 | 74 | ins.Stop() 75 | 76 | if err != nil { 77 | logger.MakeChild(logFields).ErrorIfNotCanceled(fmt.Sprintf("call to ReportingPlugin.%s errored", name), ctx, commontypes.LogFields{ 78 | "error": err, 79 | }) 80 | // failed to get data, nothing to be done 81 | var zero T 82 | return zero, false 83 | } 84 | 85 | return result, true 86 | } 87 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr3/protocol/db.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 7 | ) 8 | 9 | type PacemakerState struct { 10 | Epoch uint64 11 | HighestSentNewEpochWish uint64 12 | } 13 | 14 | type Database interface { 15 | types.ConfigDatabase 16 | 17 | ReadPacemakerState(ctx context.Context, configDigest types.ConfigDigest) (PacemakerState, error) 18 | WritePacemakerState(ctx context.Context, configDigest types.ConfigDigest, state PacemakerState) error 19 | 20 | ReadCert(ctx context.Context, configDigest types.ConfigDigest) (CertifiedPrepareOrCommit, error) 21 | WriteCert(ctx context.Context, configDigest types.ConfigDigest, cert CertifiedPrepareOrCommit) error 22 | } 23 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr3/protocol/messagebuffer.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3/protocol/ringbuffer" 4 | 5 | // We have this wrapper to deal with what appears to be a bug in the Go compiler 6 | // that prevents us from using ringbuffer.RingBuffer in the outcome generation 7 | // protocol: 8 | // offchainreporting2plus/internal/ocr3/protocol/outcome_generation.go:241:21: internal compiler error: (*ringbuffer.RingBuffer[go.shape.interface { github.com/smartcontractkit/offchain-reporting/lib/offchainreporting2plus/internal/ocr3/protocol.epoch() uint64; github.com/smartcontractkit/offchain-reporting/lib/offchainreporting2plus/internal/ocr3/protocol.processOutcomeGeneration(*github.com/smartcontractkit/offchain-reporting/lib/offchainreporting2plus/internal/ocr3/protocol.outcomeGenerationState[go.shape.struct {}], github.com/smartcontractkit/offchain-reporting/lib/commontypes.OracleID) }]).Peek(buffer, (*[9]uintptr)(.dict[3])) (type go.shape.interface { github.com/smartcontractkit/offchain-reporting/lib/offchainreporting2plus/internal/ocr3/protocol.epoch() uint64; github.com/smartcontractkit/offchain-reporting/lib/offchainreporting2plus/internal/ocr3/protocol.processOutcomeGeneration(*github.com/smartcontractkit/offchain-reporting/lib/offchainreporting2plus/internal/ocr3/protocol.outcomeGenerationState[go.shape.struct {}], github.com/smartcontractkit/offchain-reporting/lib/commontypes.OracleID) }) is not shape-identical to MessageToOutcomeGeneration[go.shape.struct {}] 9 | // Consider removing it in a future release. 10 | type MessageBuffer[RI any] ringbuffer.RingBuffer[MessageToOutcomeGeneration[RI]] 11 | 12 | func NewMessageBuffer[RI any](cap int) *MessageBuffer[RI] { 13 | return (*MessageBuffer[RI])(ringbuffer.NewRingBuffer[MessageToOutcomeGeneration[RI]](cap)) 14 | } 15 | 16 | func (rb *MessageBuffer[RI]) Length() int { 17 | return (*ringbuffer.RingBuffer[MessageToOutcomeGeneration[RI]])(rb).Length() 18 | } 19 | 20 | func (rb *MessageBuffer[RI]) Peek() MessageToOutcomeGeneration[RI] { 21 | return (*ringbuffer.RingBuffer[MessageToOutcomeGeneration[RI]])(rb).Peek() 22 | } 23 | 24 | func (rb *MessageBuffer[RI]) Pop() MessageToOutcomeGeneration[RI] { 25 | return (*ringbuffer.RingBuffer[MessageToOutcomeGeneration[RI]])(rb).Pop() 26 | } 27 | 28 | func (rb *MessageBuffer[RI]) Push(msg MessageToOutcomeGeneration[RI]) { 29 | (*ringbuffer.RingBuffer[MessageToOutcomeGeneration[RI]])(rb).Push(msg) 30 | } 31 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr3/protocol/network.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/smartcontractkit/libocr/commontypes" 7 | ) 8 | 9 | // NetworkSender sends messages to other oracles 10 | type NetworkSender[RI any] interface { 11 | // SendTo(msg, to) sends msg to "to" 12 | SendTo(msg Message[RI], to commontypes.OracleID) 13 | // Broadcast(msg) sends msg to all oracles 14 | Broadcast(msg Message[RI]) 15 | } 16 | 17 | // NetworkEndpoint sends & receives messages to/from other oracles 18 | type NetworkEndpoint[RI any] interface { 19 | NetworkSender[RI] 20 | // Receive returns channel which carries all messages sent to this oracle 21 | Receive() <-chan MessageWithSender[RI] 22 | 23 | // Start must be called before Receive. Calling Start more than once causes 24 | // panic. 25 | Start() error 26 | 27 | // Close must be called before receive. Close can be called multiple times. 28 | // Close can be called even on an unstarted NetworkEndpoint. 29 | Close() error 30 | } 31 | 32 | // SimpleNetwork is a strawman (in-memory) implementation of the Network 33 | // interface. Network channels are buffered and can queue up to 100 messages 34 | // before blocking. 35 | type SimpleNetwork[RI any] struct { 36 | chs []chan MessageWithSender[RI] // i'th channel models oracle i's network 37 | } 38 | 39 | // NewSimpleNetwork returns a SimpleNetwork for n oracles 40 | func NewSimpleNetwork[RI any](n int) *SimpleNetwork[RI] { 41 | s := SimpleNetwork[RI]{} 42 | for i := 0; i < n; i++ { 43 | s.chs = append(s.chs, make(chan MessageWithSender[RI], 100)) 44 | } 45 | return &s 46 | } 47 | 48 | // Endpoint returns the interface for oracle id's networking facilities 49 | func (net *SimpleNetwork[RI]) Endpoint(id commontypes.OracleID) (NetworkEndpoint[RI], error) { 50 | return SimpleNetworkEndpoint[RI]{ 51 | net, 52 | id, 53 | }, nil 54 | } 55 | 56 | // SimpleNetworkEndpoint is a strawman (in-memory) implementation of 57 | // NetworkEndpoint, used by SimpleNetwork 58 | type SimpleNetworkEndpoint[RI any] struct { 59 | net *SimpleNetwork[RI] // Reference back to network for all participants 60 | id commontypes.OracleID // Index of oracle this endpoint pertains to 61 | } 62 | 63 | var _ NetworkEndpoint[struct{}] = (*SimpleNetworkEndpoint[struct{}])(nil) 64 | 65 | // SendTo sends msg to oracle "to" 66 | func (end SimpleNetworkEndpoint[RI]) SendTo(msg Message[RI], to commontypes.OracleID) { 67 | log.Printf("[%v] sending to %v: %T\n", end.id, to, msg) 68 | end.net.chs[to] <- MessageWithSender[RI]{msg, end.id} 69 | } 70 | 71 | // Broadcast sends msg to all participating oracles 72 | func (end SimpleNetworkEndpoint[RI]) Broadcast(msg Message[RI]) { 73 | log.Printf("[%v] broadcasting: %T\n", end.id, msg) 74 | for _, ch := range end.net.chs { 75 | ch <- MessageWithSender[RI]{msg, end.id} 76 | } 77 | } 78 | 79 | // Receive returns a channel which carries all messages sent to the oracle 80 | func (end SimpleNetworkEndpoint[RI]) Receive() <-chan MessageWithSender[RI] { 81 | return end.net.chs[end.id] 82 | } 83 | 84 | // Start satisfies the interface 85 | func (SimpleNetworkEndpoint[RI]) Start() error { return nil } 86 | 87 | // Close satisfies the interface 88 | func (SimpleNetworkEndpoint[RI]) Close() error { return nil } 89 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr3/protocol/pool/pool.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/smartcontractkit/libocr/commontypes" 7 | ) 8 | 9 | type Pool[T any] struct { 10 | maxItemsPerSender int 11 | 12 | completedSeqNr uint64 13 | entries map[uint64]map[commontypes.OracleID]*Entry[T] 14 | count map[commontypes.OracleID]int 15 | } 16 | 17 | func NewPool[T any](maxItemsPerSender int) *Pool[T] { 18 | return &Pool[T]{ 19 | maxItemsPerSender, 20 | 21 | 0, 22 | map[uint64]map[commontypes.OracleID]*Entry[T]{}, 23 | map[commontypes.OracleID]int{}, 24 | } 25 | } 26 | 27 | type Entry[T any] struct { 28 | Item T 29 | Verified *bool 30 | } 31 | 32 | func (p *Pool[M]) ReapCompleted(completedSeqNr uint64) { 33 | for seqNr, messagesByOracleID := range p.entries { 34 | if seqNr > completedSeqNr { 35 | continue 36 | } 37 | for sender := range messagesByOracleID { 38 | p.count[sender] -= 1 39 | delete(messagesByOracleID, sender) 40 | } 41 | delete(p.entries, seqNr) 42 | } 43 | p.completedSeqNr = completedSeqNr 44 | } 45 | 46 | type PutResult string 47 | 48 | const ( 49 | PutResultOK PutResult = "ok" 50 | PutResultDuplicate PutResult = "duplicate" 51 | PutResultFull PutResult = "pool is full for sender" 52 | PutResultAlreadyCompleted PutResult = "seqNr too low" 53 | ) 54 | 55 | func (p *Pool[T]) Put(seqNr uint64, sender commontypes.OracleID, item T) PutResult { 56 | if seqNr <= p.completedSeqNr { 57 | return PutResultAlreadyCompleted 58 | } 59 | if p.maxItemsPerSender <= p.count[sender] { 60 | return PutResultFull 61 | } 62 | if p.entries[seqNr] == nil { 63 | p.entries[seqNr] = map[commontypes.OracleID]*Entry[T]{} 64 | } 65 | if p.entries[seqNr][sender] != nil { 66 | return PutResultDuplicate 67 | } 68 | p.entries[seqNr][sender] = &Entry[T]{item, nil} 69 | p.count[sender]++ 70 | return PutResultOK 71 | } 72 | 73 | func (p *Pool[M]) StoreVerified(seqNr uint64, sender commontypes.OracleID, verified bool) { 74 | if p.entries[seqNr] == nil { 75 | return 76 | } 77 | if p.entries[seqNr][sender] == nil { 78 | return 79 | } 80 | if p.entries[seqNr][sender].Verified != nil { 81 | // only store first verification result 82 | return 83 | } 84 | p.entries[seqNr][sender].Verified = &verified 85 | } 86 | 87 | func (p *Pool[M]) Entries(seqNr uint64) map[commontypes.OracleID]*Entry[M] { 88 | return p.entries[seqNr] 89 | } 90 | 91 | func (p *Pool[M]) EntriesWithMinSeqNr() map[commontypes.OracleID]*Entry[M] { 92 | if len(p.entries) == 0 { 93 | return nil 94 | } 95 | minSeqNr := uint64(math.MaxUint64) 96 | for seqNr := range p.entries { 97 | if seqNr < minSeqNr { 98 | minSeqNr = seqNr 99 | } 100 | } 101 | return p.Entries(minSeqNr) 102 | } 103 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr3/protocol/ringbuffer/ringbuffer.go: -------------------------------------------------------------------------------- 1 | package ringbuffer 2 | 3 | import "fmt" 4 | 5 | // RingBuffer implements a fixed capacity ringbuffer for items of type 6 | // T 7 | type RingBuffer[T any] struct { 8 | start int 9 | length int 10 | buffer []T 11 | } 12 | 13 | func NewRingBuffer[T any](cap int) *RingBuffer[T] { 14 | if cap <= 0 { 15 | panic(fmt.Sprintf("NewRingBuffer: cap must be positive, got %d", cap)) 16 | } 17 | return &RingBuffer[T]{ 18 | 0, 19 | 0, 20 | make([]T, cap), 21 | } 22 | } 23 | 24 | func (rb *RingBuffer[T]) Length() int { 25 | return rb.length 26 | } 27 | 28 | // Peek at the front item. Panics if there isn't one. 29 | func (rb *RingBuffer[T]) Peek() T { 30 | if rb.length == 0 { 31 | panic("Peek: buffer empty") 32 | } 33 | return rb.buffer[rb.start] 34 | } 35 | 36 | // Pop front item. Panics if there isn't one. 37 | func (rb *RingBuffer[T]) Pop() T { 38 | result := rb.Peek() 39 | var zero T 40 | rb.buffer[rb.start] = zero 41 | rb.start = (rb.start + 1) % len(rb.buffer) 42 | rb.length-- 43 | return result 44 | } 45 | 46 | // Push new item to back. If the additional item would lead 47 | // to the capacity being exceeded, remove the front item first 48 | func (rb *RingBuffer[T]) Push(item T) { 49 | if rb.length == len(rb.buffer) { 50 | rb.Pop() 51 | } 52 | rb.buffer[(rb.start+rb.length)%len(rb.buffer)] = item 53 | rb.length++ 54 | } 55 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr3/protocol/telemetry.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/smartcontractkit/libocr/commontypes" 7 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 8 | ) 9 | 10 | type TelemetrySender interface { 11 | EpochStarted( 12 | configDigest types.ConfigDigest, 13 | epoch uint32, 14 | leader commontypes.OracleID, 15 | ) 16 | 17 | RoundStarted( 18 | configDigest types.ConfigDigest, 19 | epoch uint64, 20 | seqNr uint64, 21 | round uint64, 22 | leader commontypes.OracleID, 23 | ) 24 | 25 | TransmissionScheduleComputed( 26 | configDigest types.ConfigDigest, 27 | seqNr uint64, 28 | index int, 29 | now time.Time, 30 | isOverride bool, 31 | schedule map[commontypes.OracleID]time.Duration, 32 | ok bool, 33 | ) 34 | 35 | TransmissionShouldAcceptAttestedReportComputed( 36 | configDigest types.ConfigDigest, 37 | seqNr uint64, 38 | index int, 39 | result bool, 40 | ok bool, 41 | ) 42 | 43 | TransmissionShouldTransmitAcceptedReportComputed( 44 | configDigest types.ConfigDigest, 45 | seqNr uint64, 46 | index int, 47 | result bool, 48 | ok bool, 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr3/scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3/minheap" 8 | "github.com/smartcontractkit/libocr/subprocesses" 9 | ) 10 | 11 | type itemWithDeadline[T any] struct { 12 | Item T 13 | Deadline time.Time 14 | } 15 | 16 | type Scheduler[T any] struct { 17 | subs subprocesses.Subprocesses 18 | ctx context.Context 19 | cancel context.CancelFunc 20 | 21 | in chan<- itemWithDeadline[T] 22 | out <-chan T 23 | } 24 | 25 | func NewScheduler[T any]() *Scheduler[T] { 26 | ctx, cancel := context.WithCancel(context.Background()) 27 | 28 | in := make(chan itemWithDeadline[T]) 29 | out := make(chan T) 30 | 31 | scheduler := &Scheduler[T]{ 32 | subprocesses.Subprocesses{}, 33 | ctx, 34 | cancel, 35 | 36 | in, 37 | out, 38 | } 39 | 40 | scheduler.subs.Go(func() { 41 | // create an expired timer 42 | timer := time.NewTimer(0) 43 | defer timer.Stop() 44 | <-timer.C 45 | 46 | heap := minheap.NewMinHeap(func(a, b itemWithDeadline[T]) bool { 47 | return a.Deadline.Before(b.Deadline) 48 | }) 49 | 50 | var pendingItem T 51 | var maybeOut chan<- T 52 | 53 | for { 54 | select { 55 | case item := <-in: 56 | if maybeOut == nil { 57 | peeked, ok := heap.Peek() 58 | if !ok { 59 | // the timer must be stopped already 60 | timer.Reset(time.Until(item.Deadline)) 61 | } else if peeked.Deadline.After(item.Deadline) { 62 | // we're dealing with the new minimum 63 | if timer.Stop() { 64 | // timer hasn't fired yet 65 | timer.Reset(time.Until(item.Deadline)) 66 | } // else: timer has fired. no need to do anything since 67 | // we will handle <-timer.C in an upcoming loop iteration 68 | } 69 | } 70 | heap.Push(item) 71 | case <-timer.C: 72 | popped, ok := heap.Pop() 73 | if ok { 74 | pendingItem = popped.Item 75 | maybeOut = out 76 | } else { //nolint:staticcheck 77 | // We should never enter this else branch. But if we did, it's 78 | // better to ignore the spurious firing of the timer than 79 | // to panic. 80 | // Tests should still pass with the panic not commented out. 81 | 82 | // panic("timer fired despite heap being empty, this should never happen") 83 | } 84 | case maybeOut <- pendingItem: 85 | maybeOut = nil 86 | peeked, ok := heap.Peek() 87 | if ok { 88 | timer.Reset(time.Until(peeked.Deadline)) 89 | } 90 | case <-ctx.Done(): 91 | return 92 | } 93 | } 94 | }) 95 | 96 | return scheduler 97 | } 98 | 99 | func (s *Scheduler[T]) ScheduleDeadline(item T, deadline time.Time) { 100 | select { 101 | case s.in <- itemWithDeadline[T]{item, deadline}: 102 | case <-s.ctx.Done(): 103 | } 104 | } 105 | 106 | func (s *Scheduler[T]) ScheduleDelay(item T, delay time.Duration) { 107 | s.ScheduleDeadline(item, time.Now().Add(delay)) 108 | } 109 | 110 | func (s *Scheduler[T]) Scheduled() <-chan T { 111 | return s.out 112 | } 113 | 114 | func (s *Scheduler[T]) Close() { 115 | s.cancel() 116 | s.subs.Wait() 117 | } 118 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/ocr3/serialization/telemetry.go: -------------------------------------------------------------------------------- 1 | package serialization 2 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/shim/metrics.go: -------------------------------------------------------------------------------- 1 | package shim 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | "github.com/smartcontractkit/libocr/commontypes" 6 | "github.com/smartcontractkit/libocr/internal/metricshelper" 7 | ) 8 | 9 | type serializingEndpointMetrics struct { 10 | registerer prometheus.Registerer 11 | sentMessagesTotal prometheus.Counter 12 | droppedMessagesTotal prometheus.Counter 13 | } 14 | 15 | func newSerializingEndpointMetrics( 16 | registerer prometheus.Registerer, 17 | logger commontypes.Logger, 18 | ) *serializingEndpointMetrics { 19 | sentMessagesTotal := prometheus.NewCounter(prometheus.CounterOpts{ 20 | Name: "ocr_telemetry_sent_messages_total", 21 | Help: "The number of telemetry messages sent.", 22 | }) 23 | metricshelper.RegisterOrLogError(logger, registerer, sentMessagesTotal, "ocr_telemetry_sent_messages_total") 24 | 25 | droppedMessagesTotal := prometheus.NewCounter(prometheus.CounterOpts{ 26 | Name: "ocr_telemetry_dropped_messages_total", 27 | Help: "The number of telemetry messages dropped.", 28 | }) 29 | metricshelper.RegisterOrLogError(logger, registerer, droppedMessagesTotal, "ocr_telemetry_dropped_messages_total") 30 | 31 | return &serializingEndpointMetrics{ 32 | registerer, 33 | sentMessagesTotal, 34 | droppedMessagesTotal, 35 | } 36 | } 37 | 38 | func (m *serializingEndpointMetrics) Close() { 39 | m.registerer.Unregister(m.sentMessagesTotal) 40 | m.registerer.Unregister(m.droppedMessagesTotal) 41 | } 42 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/shim/ocr3_database.go: -------------------------------------------------------------------------------- 1 | package shim 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3/protocol" 7 | "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3/serialization" 8 | "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" 9 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 10 | ) 11 | 12 | type SerializingOCR3Database struct { 13 | BinaryDb ocr3types.Database 14 | } 15 | 16 | var _ protocol.Database = (*SerializingOCR3Database)(nil) 17 | 18 | const pacemakerKey = "pacemaker" 19 | 20 | const certKey = "cert" 21 | 22 | func (db *SerializingOCR3Database) ReadConfig(ctx context.Context) (*types.ContractConfig, error) { 23 | return db.BinaryDb.ReadConfig(ctx) 24 | } 25 | 26 | func (db *SerializingOCR3Database) WriteConfig(ctx context.Context, config types.ContractConfig) error { 27 | return db.BinaryDb.WriteConfig(ctx, config) 28 | } 29 | 30 | func (db *SerializingOCR3Database) ReadPacemakerState(ctx context.Context, configDigest types.ConfigDigest) (protocol.PacemakerState, error) { 31 | raw, err := db.BinaryDb.ReadProtocolState(ctx, configDigest, pacemakerKey) 32 | if err != nil { 33 | return protocol.PacemakerState{}, err 34 | } 35 | 36 | if len(raw) == 0 { 37 | return protocol.PacemakerState{}, nil 38 | } 39 | 40 | return serialization.DeserializePacemakerState(raw) 41 | } 42 | 43 | func (db *SerializingOCR3Database) WritePacemakerState(ctx context.Context, configDigest types.ConfigDigest, state protocol.PacemakerState) error { 44 | raw, err := serialization.SerializePacemakerState(state) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | return db.BinaryDb.WriteProtocolState(ctx, configDigest, pacemakerKey, raw) 50 | } 51 | 52 | func (db *SerializingOCR3Database) ReadCert(ctx context.Context, configDigest types.ConfigDigest) (protocol.CertifiedPrepareOrCommit, error) { 53 | raw, err := db.BinaryDb.ReadProtocolState(ctx, configDigest, certKey) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | if len(raw) == 0 { 59 | return nil, nil 60 | } 61 | 62 | // This oracle wrote the PrepareOrCommit, so it's fine to trust the value. 63 | return serialization.DeserializeTrustedPrepareOrCommit(raw) 64 | } 65 | 66 | // Writing with an empty value is the same as deleting. 67 | func (db *SerializingOCR3Database) WriteCert(ctx context.Context, configDigest types.ConfigDigest, cert protocol.CertifiedPrepareOrCommit) error { 68 | if cert == nil { 69 | return db.BinaryDb.WriteProtocolState(ctx, configDigest, certKey, nil) 70 | } 71 | 72 | raw, err := serialization.SerializeCertifiedPrepareOrCommit(cert) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | return db.BinaryDb.WriteProtocolState(ctx, configDigest, certKey, raw) 78 | } 79 | -------------------------------------------------------------------------------- /offchainreporting2plus/internal/shim/reporting_plugin.go: -------------------------------------------------------------------------------- 1 | package shim 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 8 | ) 9 | 10 | // LimitCheckReportingPlugin wraps another ReportingPlugin and checks that 11 | // its outputs respect limits. We use it to surface violations to authors of 12 | // ReportingPlugins as early as possible. 13 | // 14 | // It does not check inputs since those are checked by the SerializingEndpoint. 15 | type LimitCheckReportingPlugin struct { 16 | Plugin types.ReportingPlugin 17 | Limits types.ReportingPluginLimits 18 | } 19 | 20 | var _ types.ReportingPlugin = LimitCheckReportingPlugin{} 21 | 22 | func (rp LimitCheckReportingPlugin) Query(ctx context.Context, ts types.ReportTimestamp) (types.Query, error) { 23 | query, err := rp.Plugin.Query(ctx, ts) 24 | if err != nil { 25 | return nil, err 26 | } 27 | if !(len(query) <= rp.Limits.MaxQueryLength) { 28 | return nil, fmt.Errorf("LimitCheckReportingPlugin: underlying ReportingPlugin returned oversize query (%v vs %v)", len(query), rp.Limits.MaxQueryLength) 29 | } 30 | return query, nil 31 | } 32 | 33 | func (rp LimitCheckReportingPlugin) Observation(ctx context.Context, ts types.ReportTimestamp, query types.Query) (types.Observation, error) { 34 | observation, err := rp.Plugin.Observation(ctx, ts, query) 35 | if err != nil { 36 | return nil, err 37 | } 38 | if !(len(observation) <= rp.Limits.MaxObservationLength) { 39 | return nil, fmt.Errorf("LimitCheckReportingPlugin: underlying ReportingPlugin returned oversize observation (%v vs %v)", len(observation), rp.Limits.MaxObservationLength) 40 | } 41 | return observation, nil 42 | } 43 | 44 | func (rp LimitCheckReportingPlugin) Report(ctx context.Context, ts types.ReportTimestamp, query types.Query, aos []types.AttributedObservation) (bool, types.Report, error) { 45 | shouldReport, report, err := rp.Plugin.Report(ctx, ts, query, aos) 46 | if err != nil { 47 | return false, nil, err 48 | } 49 | if !(len(report) <= rp.Limits.MaxReportLength) { 50 | return false, nil, fmt.Errorf("LimitCheckReportingPlugin: underlying ReportingPlugin returned oversize report (%v vs %v)", len(report), rp.Limits.MaxReportLength) 51 | } 52 | return shouldReport, report, nil 53 | } 54 | 55 | func (rp LimitCheckReportingPlugin) ShouldAcceptFinalizedReport(ctx context.Context, ts types.ReportTimestamp, report types.Report) (bool, error) { 56 | return rp.Plugin.ShouldAcceptFinalizedReport(ctx, ts, report) 57 | } 58 | 59 | func (rp LimitCheckReportingPlugin) ShouldTransmitAcceptedReport(ctx context.Context, ts types.ReportTimestamp, report types.Report) (bool, error) { 60 | return rp.Plugin.ShouldTransmitAcceptedReport(ctx, ts, report) 61 | } 62 | 63 | func (rp LimitCheckReportingPlugin) Close() error { 64 | return rp.Plugin.Close() 65 | } 66 | -------------------------------------------------------------------------------- /offchainreporting2plus/ocr3types/db.go: -------------------------------------------------------------------------------- 1 | package ocr3types 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 7 | ) 8 | 9 | type Database interface { 10 | types.ConfigDatabase 11 | ProtocolStateDatabase 12 | } 13 | 14 | // ProtocolStateDatabase persistently stores protocol state to survive process restarts. 15 | // Expect Write to be called far more frequently than Read. 16 | // 17 | // All its functions should be thread-safe. 18 | type ProtocolStateDatabase interface { 19 | // In case the key is not found, nil should be returned. 20 | ReadProtocolState(ctx context.Context, configDigest types.ConfigDigest, key string) ([]byte, error) 21 | // Writing with a nil value is the same as deleting. 22 | WriteProtocolState(ctx context.Context, configDigest types.ConfigDigest, key string, value []byte) error 23 | } 24 | -------------------------------------------------------------------------------- /offchainreporting2plus/ocr3types/types.go: -------------------------------------------------------------------------------- 1 | package ocr3types 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 7 | ) 8 | 9 | // ContractTransmitter sends new reports to a smart contract or other system. 10 | // 11 | // All its functions should be thread-safe. 12 | type ContractTransmitter[RI any] interface { 13 | 14 | // Transmit sends the report to the on-chain smart contract's Transmit 15 | // method. 16 | // 17 | // In most cases, implementations of this function should store the 18 | // transmission in a queue/database/..., but perform the actual 19 | // transmission (and potentially confirmation) of the transaction 20 | // asynchronously. 21 | Transmit( 22 | context.Context, 23 | types.ConfigDigest, 24 | uint64, 25 | ReportWithInfo[RI], 26 | []types.AttributedOnchainSignature, 27 | ) error 28 | 29 | // Account from which the transmitter invokes the contract 30 | FromAccount(context.Context) (types.Account, error) 31 | } 32 | 33 | // OnchainKeyring provides cryptographic signatures that need to be verifiable 34 | // on the targeted blockchain. The underlying cryptographic primitives may be 35 | // different on each chain; for example, on Ethereum one would use ECDSA over 36 | // secp256k1 and Keccak256, whereas on Solana one would use Ed25519 and SHA256. 37 | // 38 | // All its functions should be thread-safe. 39 | type OnchainKeyring[RI any] interface { 40 | // PublicKey returns the public key of the keypair used by Sign. 41 | PublicKey() types.OnchainPublicKey 42 | 43 | // Sign returns a signature over Report. 44 | Sign(types.ConfigDigest, uint64, ReportWithInfo[RI]) (signature []byte, err error) 45 | 46 | // Verify verifies a signature over ReportContext and Report allegedly 47 | // created from OnchainPublicKey. 48 | // 49 | // Implementations of this function must gracefully handle malformed or 50 | // adversarially crafted inputs. 51 | Verify(_ types.OnchainPublicKey, _ types.ConfigDigest, seqNr uint64, _ ReportWithInfo[RI], signature []byte) bool 52 | 53 | // Maximum length of a signature 54 | MaxSignatureLength() int 55 | } 56 | -------------------------------------------------------------------------------- /offchainreporting2plus/types/constants.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // The maximum number of oracles supported 4 | const MaxOracles = 31 5 | -------------------------------------------------------------------------------- /offchainreporting2plus/types/db.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | // ConfigDatabase persistently stores configuration-related information on 9 | // disk. 10 | // 11 | // All its functions should be thread-safe. 12 | type ConfigDatabase interface { 13 | ReadConfig(ctx context.Context) (*ContractConfig, error) 14 | WriteConfig(ctx context.Context, config ContractConfig) error 15 | } 16 | 17 | // Database persistently stores information on-disk. 18 | // All its functions should be thread-safe. 19 | type Database interface { 20 | ConfigDatabase 21 | 22 | ReadState(ctx context.Context, configDigest ConfigDigest) (*PersistentState, error) 23 | WriteState(ctx context.Context, configDigest ConfigDigest, state PersistentState) error 24 | 25 | StorePendingTransmission(context.Context, ReportTimestamp, PendingTransmission) error 26 | PendingTransmissionsWithConfigDigest(context.Context, ConfigDigest) (map[ReportTimestamp]PendingTransmission, error) 27 | DeletePendingTransmission(context.Context, ReportTimestamp) error 28 | DeletePendingTransmissionsOlderThan(context.Context, time.Time) error 29 | } 30 | 31 | type PendingTransmission struct { 32 | Time time.Time 33 | ExtraHash [32]byte 34 | Report Report 35 | AttributedSignatures []AttributedOnchainSignature 36 | } 37 | 38 | type PersistentState struct { 39 | Epoch uint32 40 | HighestSentEpoch uint32 41 | HighestReceivedEpoch []uint32 // length: at most MaxOracles 42 | } 43 | 44 | func (ps PersistentState) Equal(ps2 PersistentState) bool { 45 | if ps.Epoch != ps2.Epoch { 46 | return false 47 | } 48 | if ps.HighestSentEpoch != ps2.HighestSentEpoch { 49 | return false 50 | } 51 | if len(ps.HighestReceivedEpoch) != len(ps2.HighestReceivedEpoch) { 52 | return false 53 | } 54 | for i := 0; i < len(ps.HighestReceivedEpoch); i++ { 55 | if ps.HighestReceivedEpoch[i] != ps2.HighestReceivedEpoch[i] { 56 | return false 57 | } 58 | } 59 | return true 60 | } 61 | -------------------------------------------------------------------------------- /offchainreporting2plus/validate_local_config.go: -------------------------------------------------------------------------------- 1 | package offchainreporting2plus 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 9 | ) 10 | 11 | func boundTimeDuration( 12 | duration time.Duration, 13 | durationName string, 14 | min time.Duration, 15 | max time.Duration, 16 | ) error { 17 | if !(min <= duration && duration <= max) { 18 | return fmt.Errorf("%s must be between %v and %v, but is currently %v", 19 | durationName, min, max, duration) 20 | } 21 | return nil 22 | } 23 | 24 | func SanityCheckLocalConfig(c types.LocalConfig) (err error) { 25 | if c.DevelopmentMode == types.EnableDangerousDevelopmentMode { 26 | return nil 27 | } 28 | 29 | err = errors.Join(err, 30 | boundTimeDuration( 31 | c.BlockchainTimeout, 32 | "blockchain timeout", 33 | 1*time.Second, 20*time.Second, 34 | )) 35 | err = errors.Join(err, 36 | boundTimeDuration( 37 | c.ContractConfigTrackerPollInterval, 38 | "contract config tracker poll interval", 39 | 1*time.Second, 120*time.Second, 40 | )) 41 | err = errors.Join(err, 42 | boundTimeDuration( 43 | c.ContractConfigLoadTimeout, 44 | "contract config load timeout", 45 | 1*time.Second, 1*time.Hour, 46 | )) 47 | err = errors.Join(err, 48 | boundTimeDuration( 49 | c.ContractTransmitterTransmitTimeout, 50 | "contract transmitter transmit timeout", 51 | 1*time.Second, 1*time.Minute, 52 | )) 53 | err = errors.Join(err, 54 | boundTimeDuration( 55 | c.DatabaseTimeout, 56 | "database timeout", 57 | 100*time.Millisecond, 10*time.Second, 58 | )) 59 | err = errors.Join(err, 60 | boundTimeDuration( 61 | c.DefaultMaxDurationInitialization, 62 | "DefaultMaxDurationInitialization", 63 | 1*time.Second, 1*time.Hour, 64 | )) 65 | 66 | const minContractConfigConfirmations = 1 67 | const maxContractConfigConfirmations = 100 68 | if !(minContractConfigConfirmations <= c.ContractConfigConfirmations && c.ContractConfigConfirmations <= maxContractConfigConfirmations) { 69 | err = errors.Join(err, fmt.Errorf( 70 | "contract config block-depth confirmation threshold must be between %v and %v, but is currently %v", 71 | minContractConfigConfirmations, 72 | maxContractConfigConfirmations, 73 | c.ContractConfigConfirmations)) 74 | 75 | } 76 | 77 | return err 78 | } 79 | -------------------------------------------------------------------------------- /permutation/permutation.go: -------------------------------------------------------------------------------- 1 | // Package permutation generates cryptographically secure 2 | // pseudorandom permutations 3 | package permutation 4 | 5 | import ( 6 | "crypto/aes" 7 | "crypto/cipher" 8 | "encoding/binary" 9 | "math/rand" 10 | ) 11 | 12 | // Permutation generates a cryptographically secure, keyed 13 | // permutation on [0, ..., n-1] 14 | func Permutation(n int, key [16]byte) []int { 15 | var result []int 16 | for i := 0; i < n; i++ { 17 | result = append(result, i) 18 | } 19 | 20 | r := rand.New(newCryptoRandSource(key)) 21 | r.Shuffle(n, func(i int, j int) { 22 | result[i], result[j] = result[j], result[i] 23 | }) 24 | return result 25 | } 26 | 27 | type cryptoRandSource struct { 28 | stream cipher.Stream 29 | } 30 | 31 | func newCryptoRandSource(key [16]byte) *cryptoRandSource { 32 | var iv [16]byte // zero IV is fine here 33 | block, err := aes.NewCipher(key[:]) 34 | if err != nil { 35 | // assertion 36 | panic(err) 37 | } 38 | return &cryptoRandSource{cipher.NewCTR(block, iv[:])} 39 | } 40 | 41 | const int63Mask = 1<<63 - 1 42 | 43 | func (crs *cryptoRandSource) Int63() int64 { 44 | var buf [8]byte 45 | crs.stream.XORKeyStream(buf[:], buf[:]) 46 | return int64(binary.LittleEndian.Uint64(buf[:]) & int63Mask) 47 | } 48 | 49 | func (crs *cryptoRandSource) Seed(seed int64) { 50 | panic("cryptoRandSource.Seed: Not supported") 51 | } 52 | -------------------------------------------------------------------------------- /quorumhelper/quorumhelper.go: -------------------------------------------------------------------------------- 1 | package quorumhelper 2 | 3 | import ( 4 | "github.com/smartcontractkit/libocr/internal/byzquorum" 5 | "github.com/smartcontractkit/libocr/offchainreporting2/types" 6 | ) 7 | 8 | type Quorum int 9 | 10 | const ( 11 | _ Quorum = iota 12 | // Guarantees at least one honest observation 13 | QuorumFPlusOne 14 | // Guarantees an honest majority of observations 15 | QuorumTwoFPlusOne 16 | // Guarantees that all sets of observations overlap in at least one honest oracle 17 | QuorumByzQuorum 18 | // Maximal number of observations we can rely on being available 19 | QuorumNMinusF 20 | ) 21 | 22 | func ObservationCountReachesObservationQuorum(quorum Quorum, n, f int, aos []types.AttributedObservation) bool { 23 | switch quorum { 24 | case QuorumFPlusOne: 25 | return len(aos) >= f+1 26 | case QuorumTwoFPlusOne: 27 | return len(aos) >= 2*f+1 28 | case QuorumByzQuorum: 29 | return len(aos) >= byzquorum.Size(n, f) 30 | case QuorumNMinusF: 31 | return len(aos) >= n-f 32 | default: 33 | panic("Unknown quorum") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ragep2p/frame.go: -------------------------------------------------------------------------------- 1 | package ragep2p 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "encoding/binary" 7 | "fmt" 8 | 9 | "github.com/smartcontractkit/libocr/ragep2p/types" 10 | ) 11 | 12 | var errWrongLength = fmt.Errorf("frameHeader must have exactly %v bytes", frameHeaderEncodedSize) 13 | var errUnknownFrameType = fmt.Errorf("frameHeader has unknown frameType") 14 | 15 | type frameType uint8 16 | 17 | const ( 18 | _ frameType = iota 19 | frameTypeOpen 20 | frameTypeClose 21 | frameTypeData 22 | ) 23 | 24 | type frameHeader struct { 25 | Type frameType 26 | StreamID streamID 27 | PayloadLength uint32 28 | } 29 | 30 | const frameHeaderEncodedSize = 1 + 32 + 4 31 | 32 | func (fh frameHeader) Encode() []byte { 33 | buf := bytes.NewBuffer(make([]byte, 0, frameHeaderEncodedSize)) 34 | buf.WriteByte(byte(fh.Type)) 35 | buf.Write(fh.StreamID[:]) 36 | binary.Write(buf, binary.BigEndian, fh.PayloadLength) //nolint:errcheck 37 | return buf.Bytes() 38 | } 39 | 40 | func decodeFrameHeader(encoded []byte) (frameHeader, error) { 41 | if len(encoded) != frameHeaderEncodedSize { 42 | return frameHeader{}, errWrongLength 43 | } 44 | typ := frameType(encoded[0]) 45 | switch typ { 46 | case frameTypeOpen: 47 | case frameTypeClose: 48 | case frameTypeData: 49 | default: 50 | return frameHeader{}, errUnknownFrameType 51 | } 52 | var streamId streamID 53 | copy(streamId[:], encoded[1:33]) 54 | payloadLength := binary.BigEndian.Uint32(encoded[33:frameHeaderEncodedSize]) 55 | return frameHeader{ 56 | typ, 57 | streamId, 58 | payloadLength, 59 | }, nil 60 | } 61 | 62 | func getStreamID(self types.PeerID, other types.PeerID, name string) streamID { 63 | if bytes.Compare(self[:], other[:]) < 0 { 64 | return getStreamID(other, self, name) 65 | } 66 | 67 | h := sha256.New() 68 | h.Write(self[:]) 69 | h.Write(other[:]) 70 | // this is fine because self and other are of constant length. if more than 71 | // one variable length item is ever added here, we should also hash lengths 72 | // to prevent collisions. 73 | h.Write([]byte(name)) 74 | 75 | var result streamID 76 | copy(result[:], h.Sum(nil)) 77 | return result 78 | } 79 | -------------------------------------------------------------------------------- /ragep2p/internal/knock/knock.go: -------------------------------------------------------------------------------- 1 | package knock 2 | 3 | import ( 4 | "crypto/ed25519" 5 | "fmt" 6 | 7 | "github.com/smartcontractkit/libocr/ragep2p/types" 8 | ) 9 | 10 | const domainSeparator = "ragep2p 1.0.0 knock knock" 11 | const version = byte(0x02) // 0x2 rather than 0x1 to prevent accidental connection with previous OCR networking 12 | 13 | // knock = version (1 byte) || pk (ed25519.PublicKeySize) || sig (ed25519.SignatureSize) 14 | const KnockSize = 1 + ed25519.PublicKeySize + ed25519.SignatureSize 15 | 16 | func messageToSign(peerID types.PeerID) []byte { 17 | msg := make([]byte, 0, len(domainSeparator)+len(peerID)) 18 | msg = append(msg, []byte(domainSeparator)...) 19 | msg = append(msg, peerID[:]...) 20 | return msg 21 | } 22 | 23 | // Builds a knock message based on the PeerID of the node being dialed (other), 24 | // the dialing node (self). The dialing node's secretKey is used for signing the 25 | // message and must correspond to self. 26 | // 27 | // Returns a knock message exactly KnockSize bytes long. 28 | func BuildKnock(other types.PeerID, self types.PeerID, keyring types.PeerKeyring) ([]byte, error) { 29 | msg := messageToSign(other) 30 | sig, err := keyring.Sign(msg) 31 | if err != nil { 32 | return nil, fmt.Errorf("keyring failed to sign knock message: %w", err) 33 | } 34 | 35 | knock := make([]byte, 0, KnockSize) 36 | knock = append(knock, version) 37 | knock = append(knock, self[:]...) 38 | knock = append(knock, sig...) 39 | return knock, nil 40 | } 41 | 42 | var ( 43 | ErrSizeMismatch = fmt.Errorf("knock size mismatch") 44 | ErrFromSelfDial = fmt.Errorf("knock from self-dial") 45 | ErrInvalidSignature = fmt.Errorf("knock has invalid signature") 46 | ) 47 | 48 | // Verifies a knock message allegedly destined to self. If the message is valid, 49 | // returns the PeerId of the sender. Otherwise returns nil and an error. 50 | func VerifyKnock(self types.PeerID, knock []byte) (*types.PeerID, error) { 51 | if len(knock) != KnockSize { 52 | return nil, fmt.Errorf("knock has wrong length %v, expected %v", len(knock), KnockSize) 53 | } 54 | 55 | if knock[0] != version { 56 | return nil, fmt.Errorf("knock has wrong version %v, expected %v", knock[0], version) 57 | } 58 | knock = knock[1:] 59 | 60 | var other types.PeerID 61 | if len(other) != copy(other[:], knock[:ed25519.PublicKeySize]) { 62 | return nil, ErrSizeMismatch 63 | } 64 | 65 | if other == self { 66 | return nil, ErrFromSelfDial 67 | } 68 | 69 | msg := messageToSign(self) 70 | sig := knock[ed25519.PublicKeySize:] 71 | if !ed25519.Verify(ed25519.PublicKey(other[:]), msg, sig) { 72 | return nil, ErrInvalidSignature 73 | } 74 | 75 | return &other, nil 76 | } 77 | -------------------------------------------------------------------------------- /ragep2p/internal/msgbuf/ringbuffer.go: -------------------------------------------------------------------------------- 1 | package msgbuf 2 | 3 | // MessageBuffer implements a fixed capacity ringbuffer for items of type 4 | // []byte. 5 | type MessageBuffer struct { 6 | start int 7 | length int 8 | buffer [][]byte 9 | } 10 | 11 | func NewMessageBuffer(cap int) *MessageBuffer { 12 | return &MessageBuffer{ 13 | 0, 14 | 0, 15 | make([][]byte, cap), 16 | } 17 | } 18 | 19 | // Peek at the front item 20 | func (rb *MessageBuffer) Peek() []byte { 21 | if rb.length == 0 { 22 | return nil 23 | } else { 24 | return rb.buffer[rb.start] 25 | } 26 | } 27 | 28 | // Pop front item 29 | func (rb *MessageBuffer) Pop() []byte { 30 | result := rb.Peek() 31 | if result != nil { 32 | rb.buffer[rb.start] = nil 33 | rb.start = (rb.start + 1) % len(rb.buffer) 34 | rb.length-- 35 | } 36 | return result 37 | } 38 | 39 | // Push new item to back. If the additional item would lead to the capacity 40 | // being exceeded, remove the front item first. 41 | // 42 | // Returns the removed front item, or nil. 43 | func (rb *MessageBuffer) Push(msg []byte) []byte { 44 | var result []byte 45 | 46 | if msg == nil { 47 | panic("cannot push nil") 48 | } 49 | if rb.length == len(rb.buffer) { 50 | result = rb.Pop() 51 | } 52 | rb.buffer[(rb.start+rb.length)%len(rb.buffer)] = msg 53 | rb.length++ 54 | return result 55 | } 56 | -------------------------------------------------------------------------------- /ragep2p/internal/mtls/common.go: -------------------------------------------------------------------------------- 1 | package mtls 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rand" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "fmt" 9 | "math/big" 10 | 11 | "github.com/smartcontractkit/libocr/ragep2p/types" 12 | ) 13 | 14 | // Generates a minimal certificate (that wouldn't be considered valid outside this telemetry networking protocol) 15 | // from a PeerKeyring. 16 | func NewMinimalX509CertFromKeyring(keyring types.PeerKeyring) (tls.Certificate, error) { 17 | template := x509.Certificate{ 18 | SerialNumber: big.NewInt(0), // serial number must be set, so we set it to 0 19 | } 20 | 21 | // x509 args are of type any, even though crypto.Signer is required 22 | var signer crypto.Signer = &peerKeyringCryptoSignerAdapter{keyring} 23 | 24 | encodedCert, err := x509.CreateCertificate(rand.Reader, &template, &template, signer.Public(), signer) 25 | if err != nil { 26 | return tls.Certificate{}, fmt.Errorf("x509.CreateCertificate: %w", err) 27 | } 28 | 29 | // Uncomment this if you want to get an encoded cert you can feed into openssl x509 etc... 30 | // 31 | // var buf bytes.Buffer 32 | // if err := pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: encodedCert}); err != nil { 33 | // log.Fatalf("Failed to encode cert into pem format: %v", err) 34 | // } 35 | // fmt.Printf("pubkey: %x\nencodedCert: %v\n", signer.Public(), buf.String()) 36 | 37 | return tls.Certificate{ 38 | Certificate: [][]byte{encodedCert}, 39 | 40 | PrivateKey: signer, 41 | SupportedSignatureAlgorithms: []tls.SignatureScheme{tls.Ed25519}, 42 | }, nil 43 | } 44 | 45 | func VerifyCertMatchesPubKey(publicKey [32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { 46 | return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { 47 | if len(rawCerts) != 1 { 48 | return fmt.Errorf("required exactly one client certificate") 49 | } 50 | cert, err := x509.ParseCertificate(rawCerts[0]) 51 | if err != nil { 52 | return err 53 | } 54 | pk, err := PubKeyFromCert(cert) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | if pk != publicKey { 60 | return fmt.Errorf("unknown public key on cert: %x doesn't match expected public key %x", pk, publicKey) 61 | } 62 | 63 | return nil 64 | } 65 | } 66 | 67 | func PubKeyFromCert(cert *x509.Certificate) (pk types.PeerPublicKey, err error) { 68 | if cert.PublicKeyAlgorithm != x509.Ed25519 { 69 | return pk, fmt.Errorf("require ed25519 public key") 70 | } 71 | return types.PeerPublicKeyFromGenericPublicKey(cert.PublicKey) 72 | } 73 | -------------------------------------------------------------------------------- /ragep2p/internal/mtls/crypto_signer_adapter.go: -------------------------------------------------------------------------------- 1 | package mtls 2 | 3 | import ( 4 | "crypto" 5 | "crypto/ed25519" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/smartcontractkit/libocr/ragep2p/types" 10 | ) 11 | 12 | type peerKeyringCryptoSignerAdapter struct { 13 | keyring types.PeerKeyring 14 | } 15 | 16 | // Public implements crypto.Signer. 17 | func (p *peerKeyringCryptoSignerAdapter) Public() crypto.PublicKey { 18 | pk := p.keyring.PublicKey() 19 | return ed25519.PublicKey(pk[:]) 20 | } 21 | 22 | // Sign implements crypto.Signer. 23 | func (p *peerKeyringCryptoSignerAdapter) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { 24 | // We can safely ignore the io.Reader providing randomness, since we use 25 | // deterministic Ed25519 here. 26 | if opts != crypto.Hash(0) { 27 | return nil, fmt.Errorf("unexpected SignerOpts for Ed25519: %v", opts) 28 | } 29 | return p.keyring.Sign(digest) 30 | } 31 | 32 | var _ crypto.Signer = &peerKeyringCryptoSignerAdapter{} 33 | -------------------------------------------------------------------------------- /ragep2p/internal/ratelimitedconn/rate_limited_conn.go: -------------------------------------------------------------------------------- 1 | package ratelimitedconn 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/prometheus/client_golang/prometheus" 8 | "github.com/smartcontractkit/libocr/commontypes" 9 | ) 10 | 11 | type Limiter interface { 12 | Allow(n int) bool 13 | } 14 | 15 | // TODO: would it make sense to merge this with the connRateLimiter? 16 | type RateLimitedConn struct { 17 | net.Conn 18 | bandwidthLimiter Limiter 19 | logger commontypes.Logger 20 | readBytesTotal prometheus.Counter 21 | writtenBytesTotal prometheus.Counter 22 | rateLimitingEnabled bool 23 | } 24 | 25 | var _ net.Conn = (*RateLimitedConn)(nil) 26 | 27 | func NewRateLimitedConn( 28 | conn net.Conn, 29 | bandwidthLimiter Limiter, 30 | logger commontypes.Logger, 31 | readBytesTotal prometheus.Counter, 32 | writtenBytesTotal prometheus.Counter, 33 | ) *RateLimitedConn { 34 | return &RateLimitedConn{ 35 | conn, 36 | bandwidthLimiter, 37 | logger, 38 | readBytesTotal, 39 | writtenBytesTotal, 40 | false, 41 | } 42 | } 43 | 44 | // EnableRateLimiting is not thread-safe! 45 | func (r *RateLimitedConn) EnableRateLimiting() { 46 | r.rateLimitingEnabled = true 47 | } 48 | 49 | func (r *RateLimitedConn) Read(b []byte) (n int, err error) { 50 | n, err = r.Conn.Read(b) 51 | r.readBytesTotal.Add(float64(n)) 52 | if !r.rateLimitingEnabled { 53 | return n, err 54 | } 55 | 56 | nBytesAllowed := r.bandwidthLimiter.Allow(n) 57 | if nBytesAllowed { 58 | return n, err 59 | } 60 | // kill the conn: close it and emit an error 61 | _ = r.Conn.Close() // ignore error, there's not much we can with it here 62 | // TODO: log the limits here 63 | r.logger.Error("inbound data exceeded rate limit, connection closed", commontypes.LogFields{ 64 | // "tokenBucketRefillRate": r.bandwidthLimiter.Limit(), 65 | // "tokenBucketSize": r.bandwidthLimiter.Burst(), 66 | "bytesRead": n, 67 | "readError": err, // This error may not be null, we're adding it here to not miss it. 68 | }) 69 | return 0, fmt.Errorf("inbound data exceeded rate limit, connection closed") 70 | } 71 | 72 | func (r *RateLimitedConn) Write(b []byte) (n int, err error) { 73 | n, err = r.Conn.Write(b) 74 | r.writtenBytesTotal.Add(float64(n)) 75 | return n, err 76 | } 77 | -------------------------------------------------------------------------------- /ragep2p/loggers/logrus.go: -------------------------------------------------------------------------------- 1 | package loggers 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "github.com/smartcontractkit/libocr/commontypes" 6 | ) 7 | 8 | var _ commontypes.Logger = LogrusLogger{} 9 | 10 | type LogrusLogger struct { 11 | logger *logrus.Logger 12 | } 13 | 14 | func MakeLogrusLogger() LogrusLogger { 15 | logger := logrus.New() 16 | logger.SetLevel(logrus.TraceLevel) 17 | return LogrusLogger{ 18 | logger, 19 | } 20 | } 21 | 22 | func (l LogrusLogger) Trace(msg string, fields commontypes.LogFields) { 23 | l.logger.WithFields(logrus.Fields(fields)).Trace(msg) 24 | } 25 | 26 | func (l LogrusLogger) Debug(msg string, fields commontypes.LogFields) { 27 | l.logger.WithFields(logrus.Fields(fields)).Debug(msg) 28 | } 29 | 30 | func (l LogrusLogger) Info(msg string, fields commontypes.LogFields) { 31 | l.logger.WithFields(logrus.Fields(fields)).Info(msg) 32 | } 33 | 34 | func (l LogrusLogger) Warn(msg string, fields commontypes.LogFields) { 35 | l.logger.WithFields(logrus.Fields(fields)).Warn(msg) 36 | } 37 | 38 | func (l LogrusLogger) Error(msg string, fields commontypes.LogFields) { 39 | l.logger.WithFields(logrus.Fields(fields)).Error(msg) 40 | } 41 | 42 | func (l LogrusLogger) Critical(msg string, fields commontypes.LogFields) { 43 | l.logger.WithFields(logrus.Fields(fields)).Error("CRITICAL: " + msg) 44 | } 45 | -------------------------------------------------------------------------------- /ragep2p/tls_config.go: -------------------------------------------------------------------------------- 1 | package ragep2p 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | ) 7 | 8 | func newTLSConfig(cert tls.Certificate, verifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error) *tls.Config { 9 | return &tls.Config{ 10 | Certificates: []tls.Certificate{cert}, 11 | ClientAuth: tls.RequireAnyClientCert, 12 | 13 | // Since our clients use self-signed certs, we skip verification here. 14 | // Instead, we use VerifyPeerCertificate for our own check 15 | InsecureSkipVerify: true, 16 | 17 | MaxVersion: tls.VersionTLS13, 18 | MinVersion: tls.VersionTLS13, 19 | 20 | VerifyPeerCertificate: verifyPeerCertificate, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /subprocesses/subprocesses.go: -------------------------------------------------------------------------------- 1 | // Package subprocesses keeps track of concurrent processes, 2 | // for coordination of cleanly shutting down systems of goroutines. This is a 3 | // stripped-down version of errgroup.Group, motivated by the fact that allowing 4 | // a single process to shut down the entire system by returning an error is 5 | // quite fragile. 6 | package subprocesses 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | type Subprocesses struct { 16 | wg sync.WaitGroup 17 | } 18 | 19 | // Wait blocks until all function calls from the Go method have returned. 20 | func (s *Subprocesses) Wait() { 21 | s.wg.Wait() 22 | } 23 | 24 | // Go calls the given function in a new goroutine. 25 | func (s *Subprocesses) Go(f func()) { 26 | s.wg.Add(1) 27 | go func() { 28 | defer s.wg.Done() 29 | f() 30 | }() 31 | } 32 | 33 | // BlockForAtMost invokes f and blocks for at most duration d before returning, 34 | // regardless of whether f finished or not, or the passed in ctx is cancelled. 35 | // If f finished, returns true. 36 | // Otherwise, returns false. 37 | func (s *Subprocesses) BlockForAtMost(ctx context.Context, d time.Duration, f func(context.Context)) (ok bool) { 38 | done := make(chan struct{}) 39 | childCtx, childCancel := context.WithTimeout(ctx, d) 40 | defer childCancel() 41 | s.Go(func() { 42 | f(childCtx) 43 | close(done) 44 | }) 45 | t := time.NewTimer(d) 46 | defer t.Stop() 47 | 48 | select { 49 | case <-done: 50 | return true 51 | case <-t.C: 52 | return false 53 | } 54 | } 55 | 56 | // BlockForAtMostMany invokes all fs in parallel and blocks for at most duration 57 | // d before returning, regardless of whether all fs finished or not, or the 58 | // passed in ctx is cancelled. If all fs finished, returns true, [true, ..., 59 | // true]. Otherwise, returns false, and a boolean slice indicating which fs 60 | // timed out. 61 | func (s *Subprocesses) BlockForAtMostMany(ctx context.Context, d time.Duration, fs ...func(context.Context)) (ok bool, oks []bool) { 62 | done := make(chan int, len(fs)) 63 | childCtx, childCancel := context.WithTimeout(ctx, d) 64 | defer childCancel() 65 | for i, f := range fs { 66 | iCopy, fCopy := i, f 67 | s.Go(func() { 68 | fCopy(childCtx) 69 | done <- iCopy 70 | }) 71 | } 72 | t := time.NewTimer(d) 73 | defer t.Stop() 74 | 75 | oks = make([]bool, len(fs)) 76 | doneCount := 0 77 | for { 78 | select { 79 | case i := <-done: 80 | oks[i] = true 81 | doneCount++ 82 | if doneCount == len(fs) { 83 | return true, oks 84 | } 85 | case <-t.C: 86 | return false, oks 87 | } 88 | } 89 | } 90 | 91 | // RepeatWithCancel repeats f with the specified interval. Cancel if ctx.Done is signaled 92 | func (s *Subprocesses) RepeatWithCancel(name string, interval time.Duration, ctx context.Context, f func()) { 93 | s.Go(func() { 94 | ticker := time.NewTicker(interval) 95 | defer ticker.Stop() 96 | 97 | for { 98 | select { 99 | case <-ctx.Done(): 100 | fmt.Println("canceling", name) 101 | return 102 | case <-ticker.C: 103 | f() 104 | } 105 | } 106 | }) 107 | } 108 | --------------------------------------------------------------------------------