smartcontractkit / chainlink

node of the decentralized oracle network, bridging on and off-chain computation
https://chain.link
Other
7.02k stars 1.71k forks source link

[NODE] panic: slice bounds out of range [:2] error returning string value with length < 2 chars from custom external adapter #5728

Closed iangriggs closed 2 years ago

iangriggs commented 2 years ago

Description Custom external adapter returns string whose length is less than 2 chars causes the Chainlink node to crash with error:

panic: runtime error: slice bounds out of range [:2] with length 0

Basic Information Chainlink node is running locally on Docker on mac. Custom external adapter is a locally running express endpoint that returns multiple parameters to a ChainlinkClient contract that is configured for a Operator.sol contract instance.

Full error:

panic: runtime error: slice bounds out of range [:2] with length 1

goroutine 19198 [running]: github.com/smartcontractkit/chainlink/core/services/pipeline.convertToETHABIBytes(0x215b0c8, 0x17dd2e0, 0x17cf320, 0xc00355df90, 0x98, 0x20, 0x0, 0x0, 0x0, 0x0) /chainlink/core/services/pipeline/common_eth.go:228 +0x11d0 github.com/smartcontractkit/chainlink/core/services/pipeline.convertToETHABIType(0x17cf320, 0xc00355df90, 0x0, 0x20, 0x8, 0xc0010dd9a0, 0x7, 0x0, 0x0, 0x0, ...) /chainlink/core/services/pipeline/common_eth.go:137 +0x1965 github.com/smartcontractkit/chainlink/core/services/pipeline.(ETHABIEncodeTask).Run(0xc001f36000, 0x2136570, 0xc002611500, 0xc001d03f50, 0xc002e37180, 0x2, 0x2, 0xc0001dea80, 0xc, 0xc, ...) /chainlink/core/services/pipeline/task.eth_abi_encode.go:58 +0x678 github.com/smartcontractkit/chainlink/core/services/pipeline.(runner).executeTaskRun(0xc0003eea50, 0x2136570, 0xc002611500, 0x6, 0xc002614000, 0x676, 0x3102630, 0xed96243ed, 0x2daa060, 0x0, ...) /chainlink/core/services/pipeline/runner.go:382 +0x2c5 github.com/smartcontractkit/chainlink/core/services/pipeline.(runner).run.func1(0xc0003eea50, 0x2136570, 0xc002611500, 0xc001b40000, 0xc0005a4a90, 0x0, 0x0, 0xff, 0xc000044015, 0xa, ...) /chainlink/core/services/pipeline/runner.go:284 +0x105 created by github.com/smartcontractkit/chainlink/core/services/pipeline.(runner).run /chainlink/core/services/pipeline/runner.go:283 +0x2b6

Environment Variables ALLOW_ORIGINS: * BLOCK_BACKFILL_DEPTH: 10 BLOCK_HISTORY_ESTIMATOR_BLOCK_DELAY: 0 BLOCK_HISTORY_ESTIMATOR_BLOCK_HISTORY_SIZE: 0 BLOCK_HISTORY_ESTIMATOR_TRANSACTION_PERCENTILE: 0 BRIDGE_RESPONSE_URL: ETH_CHAIN_ID: 43113 CLIENT_NODE_URL: http://localhost:6688 DATABASE_BACKUP_FREQUENCY: 1h0m0s DATABASE_BACKUP_MODE: none DATABASE_MAXIMUM_TX_DURATION: 30m0s DATABASE_TIMEOUT: DEFAULT_HTTP_LIMIT: 32768 DEFAULT_HTTP_TIMEOUT: 30m0s CHAINLINK_DEV: false ETH_DISABLED: false ETH_HTTP_URL: ETH_SECONDARY_URLS: [] ETH_URL: wss://speedy-nodes-nyc.moralis.io/.../avalanche/testnet/ws EXPLORER_URL: FM_DEFAULT_TRANSACTION_QUEUE_DEPTH: 1 FEATURE_EXTERNAL_INITIATORS: false FEATURE_OFFCHAIN_REPORTING: false GAS_ESTIMATOR_MODE: INSECURE_FAST_SCRYPT: false JSON_CONSOLE: false JOB_PIPELINE_REAPER_INTERVAL: 1h0m0s JOB_PIPELINE_REAPER_THRESHOLD: 24h0m0s KEEPER_DEFAULT_TRANSACTION_QUEUE_DEPTH: 1 KEEPER_MAXIMUM_GRACE_PERIOD: 0 KEEPER_MINIMUM_REQUIRED_CONFIRMATIONS: 0 KEEPER_REGISTRY_CHECK_GAS_OVERHEAD: 0 KEEPER_REGISTRY_PERFORM_GAS_OVERHEAD: 0 KEEPER_REGISTRY_SYNC_INTERVAL: LINK_CONTRACT_ADDRESS: FLAGS_CONTRACT_ADDRESS: LAYER_2_TYPE: LOG_LEVEL: debug LOG_SQL_MIGRATIONS: true LOG_SQL: false LOG_TO_DISK: true OCR_BOOTSTRAP_CHECK_INTERVAL: 20s TRIGGER_FALLBACK_DB_POLL_INTERVAL: 30s OCR_CONTRACT_TRANSMITTER_TRANSMIT_TIMEOUT: 10s OCR_DATABASE_TIMEOUT: 10s OCR_DEFAULT_TRANSACTION_QUEUE_DEPTH: 1 OCR_INCOMING_MESSAGE_BUFFER_SIZE: 10 P2P_BOOTSTRAP_PEERS: [] P2P_LISTEN_IP: 0.0.0.0 P2P_LISTEN_PORT: P2P_NETWORKING_STACK: V1 P2P_PEER_ID: P2PV2_ANNOUNCE_ADDRESSES: [] P2PV2_BOOTSTRAPPERS: [] P2PV2_DELTA_DIAL: 15s P2PV2_DELTA_RECONCILE: 1m0s P2PV2_LISTEN_ADDRESSES: [] OCR_OUTGOING_MESSAGE_BUFFER_SIZE: 10 OCR_NEW_STREAM_TIMEOUT: 10s OCR_DHT_LOOKUP_INTERVAL: 10 OCR_TRACE_LOGGING: false CHAINLINK_PORT: 6688 REAPER_EXPIRATION: 240h0m0s REPLAY_FROM_BLOCK: -1 ROOT: /chainlink SECURE_COOKIES: false SESSION_TIMEOUT: 15m0s TELEMETRY_INGRESS_LOGGING: false TELEMETRY_INGRESS_SERVER_PUB_KEY: TELEMETRY_INGRESS_URL: CHAINLINK_TLS_HOST: CHAINLINK_TLS_PORT: 0 CHAINLINK_TLS_REDIRECT: false

Steps to Reproduce BRIDGE INFO------

Name | mybridge URL | http://host.docker.internal:8080 Confirmations | 0 Minimum Contract Payment | 0

JOB SPEC--------

type = "directrequest" schemaVersion = 1 name = "Get > Multi" contractAddress = "0xE52F4aedAb0581df41fB5aBA4d46dda213C962f3" maxTaskDuration = "0s" observationSource = """ decode_log [type="ethabidecodelog" abi="OracleRequest(bytes32 indexed specId, address requester, bytes32 requestId, uint256 payment, address callbackAddr, bytes4 callbackFunctionId, uint256 cancelExpiration, uint256 dataVersion, bytes data)" data="$(jobRun.logData)" topics="$(jobRun.logTopics)"]

decode_cbor [type="cborparse" data="$(decode_log.data)"] fetch [type="bridge" name="mybridge" requestData="{\"id\":$(jobSpec.externalJobID),\"data\":{\"param1\":$(decode_cbor.param1),\"request_id\":$(decode_log.requestId)}}"]

decode_log -> decode_cbor -> fetch

parse_title [type="jsonparse" path="title" data="$(fetch)"] parse_duration [type="jsonparse" path="duration" data="$(fetch)"]

fetch -> parse_title fetch -> parse_duration

parse_title -> encode_data parse_duration -> encode_data

encode_data [type="ethabiencode" abi="(bytes32 _requestId, bytes32 _title, bytes32 _duration)" data="{ \"_requestId\": $(decode_log.requestId), \"_title\": $(parse_title), \"_duration\": $(parse_duration) }"] encode_tx [type="ethabiencode" abi="fulfillOracleRequest2(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes calldata data)" data="{\"requestId\": $(decode_log.requestId), \"payment\": $(decode_log.payment), \"callbackAddress\": $(decode_log.callbackAddr), \"callbackFunctionId\": $(decode_log.callbackFunctionId), \"expiration\": $(decode_log.cancelExpiration), \"data\": $(encode_data)}" ] submit_tx [type="ethtx" to="0xE52F4aedAb0581df41fB5aBA4d46dda213C962f3" data="$(encode_tx)"]

encode_data -> encode_tx -> submit_tx

"""

API Consumer Contract--------

// SPDX-License-Identifier: MIT pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";

contract APIConsumerIssue is ChainlinkClient { using Chainlink for Chainlink.Request;

address private oracle;
bytes32 private jobId;
uint256 private fee;

bytes32 public title;
bytes32 public duration;

struct AudioFile {
    bytes32 title;
    bytes32 duration;
}

constructor() {
    setChainlinkToken(0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846);
    oracle = 0xE52F4aedAb0581df41fB5aBA4d46dda213C962f3;
    setChainlinkOracle(oracle);
    jobId = "2ae675f68d5f43a1877030a839c1df39";
    fee = 0.1 * 10 ** 18;
}

function requestData(string memory param1) public returns (bytes32) 
{
    Chainlink.Request memory request = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);
    request.add("param1", param1);
    return sendChainlinkRequest(request, fee);
}

function fulfill(bytes32 _requestId, bytes32 _title, bytes32 _duration) public recordChainlinkFulfillment(_requestId)
{
    title= _title;
    duration = _duration;
}

// function withdrawLink() external {} - Implement a withdraw function to avoid locking your LINK in the contract

}

Operator.sol contract------------

// SPDX-License-Identifier: MIT pragma solidity 0.7.0; import "@chainlink/contracts/src/v0.7/Operator.sol";

External adapter -----

Using template from https://github.com/thodges-gh/CL-EA-NodeJS-Template

Replace createRequest method in index.js with:

const createRequest = async (input, callback) => {
  callback(200, {
    title: "a title",
    duration: "1",
  });
};

Steps:

  1. Setup environment as above
  2. Fund APIConsumerIssue with LINK
  3. Call requestData function of APIConsumerIssue.sol with any string parameter value
  4. Notice Chainlink node crashes/panic where duration in createRequest above is less than 2 characters.
  5. Notice Chainlink node does not crash/panic when duration is 2 character or greater in length
iangriggs commented 2 years ago

I'm not familiar with golang but code in line here:

https://github.com/smartcontractkit/chainlink/blob/0c50abc53be75ebc1594578774c1aa2a2e72d9aa/core/services/pipeline/common_eth.go#L273

assumes string is at least 2 characters.

Possible code fix is replacing if s[:2] == "0x" with if strings.hasPrefix(s,"0x") ?

As I said not a golang dev so using intuition and google ;-)

samsondav commented 2 years ago

Thanks @iangriggs this will be fixed in 1.1.0 when that is released