WIP: WIP-0018 Layer: Consensus (hard fork) Title: Remove message argument from UnhandledIntercept RADON error Authors: Tomasz Polaczyk Discussions-To: `#dev-general` channel on Witnet Community's Discord server Status: Final Type: Standards Track Created: 2021-09-03 License: BSD-2-Clause
The UnhandledIntercept
RADON error is used to represent RADON script execution errors that do not have a corresponding error code. To help with the identification of unhandled errors, the UnhandledIntercept
error includes an error message. This message is included in the tally transaction, and it must be agreed upon by all the nodes in the network. Therefore, any changes to this message may result in hard forks. This proposal removes the message argument to avoid any future problems.
During the implementation of [WIP-0017][WIP-0017], we have noticed that renaming a RadError
may invalidate some tally transactions, creating a hard fork. The reason for that is the UnhandledIntercept
error which has one extra argument, the "message", which is implemented in Rust as:
format!("inner: {:?}", inner)
In other words, it creates a string that contains the debug representation of the inner error. The debug representation is created by the Rust compiler, and it may change between Rust versions. And the inner error is supposed to be an implementation detail of witnet-rust, so it doesn't make much sense for it to be part of consensus-critical code. This gives us a good reason to remove this message.
This message may also appear during the retrieval and aggregation stages. In that case, the revealed bytes can be different for different witnesses, so it is not consensus-critical and changing the message is not a breaking change. Therefore the message argument should only be removed in the tally stage when serializing the result of the tally script.
There are two kinds of UnhandledIntercept
errors, depending on where they are created:
- Created in tally
- Created in retrieval/aggregation
This document focuses on those errors that are created in the tally stage because they are the consensus-critical. As an example, see data request 63c1ff0bacd24676bd71fa2bb984e8faee22cd8c73b14b83c57149a792dc315c
. In that request, every node reports a different string, and the mode operator returns an error because there is no "most common string". This is the tally using the old validation rules:
Tally: RadonTypes::RadonError(UnhandledIntercept { inner: None, message: Some("inner: ModeTie { values: RadonArray { value: [String(RadonString { value: \"149.28.82.190\" }), String(RadonString { value: \"138.201.132.240\" }), String(RadonString { value: \"199.192.30.223\" }), String(RadonString { value: \"159.89.7.193\" }), String(RadonString { value: \"138.201.65.62\" }), String(RadonString { value: \"195.201.61.247\" }), String(RadonString { value: \"149.28.82.190\" }), String(RadonString { value: \"138.201.132.40\" }), String(RadonString { value: \"136.243.93.244\" }), String(RadonString { value: \"88.99.68.56\" }), String(RadonString { value: \"88.198.7.148\" }), String(RadonString { value: \"178.63.54.115\" }), String(RadonString { value: \"168.119.172.143\" }), String(RadonString { value: \"213.239.234.132\" }), String(RadonString { value: \"63.250.47.141\" }), String(RadonString { value: \"207.154.238.90\" }), String(RadonString { value: \"168.119.15.50\" }), String(RadonString { value: \"136.244.77.174\" }), String(RadonString { value: \"94.130.223.45\" }), String(RadonString { value: \"136.243.109.250\" }), String(RadonString { value: \"168.119.119.170\" }), String(RadonString { value: \"188.40.94.103\" }), String(RadonString { value: \"46.4.75.56\" }), String(RadonString { value: \"138.201.46.114\" }), String(RadonString { value: \"168.119.119.170\" }), String(RadonString { value: \"138.201.132.40\" }), String(RadonString { value: \"95.111.244.179\" }), String(RadonString { value: \"135.181.60.181\" }), String(RadonString { value: \"68.183.39.75\" }), String(RadonString { value: \"159.69.75.174\" }), String(RadonString { value: \"159.69.72.188\" }), String(RadonString { value: \"78.46.40.149\" }), String(RadonString { value: \"176.9.66.252\" }), String(RadonString { value: \"176.9.120.66\" }), String(RadonString { value: \"148.251.128.26\" }), String(RadonString { value: \"78.46.91.66\" }), String(RadonString { value: \"135.181.60.189\" }), String(RadonString { value: \"142.93.250.57\" }), String(RadonString { value: \"49.12.73.196\" }), String(RadonString { value: \"178.63.70.230\" }), String(RadonString { value: \"159.89.7.193\" }), String(RadonString { value: \"176.9.3.164\" }), String(RadonString { value: \"88.99.68.109\" }), String(RadonString { value: \"116.202.218.95\" }), String(RadonString { value: \"168.119.172.143\" }), String(RadonString { value: \"85.114.132.71\" }), String(RadonString { value: \"167.99.246.148\" }), String(RadonString { value: \"168.119.119.170\" }), String(RadonString { value: \"138.201.66.13\" }), String(RadonString { value: \"136.243.94.171\" }), String(RadonString { value: \"88.99.68.109\" }), String(RadonString { value: \"46.4.88.189\" }), String(RadonString { value: \"135.181.60.183\" }), String(RadonString { value: \"138.201.83.20\" }), String(RadonString { value: \"149.7.16.186\" }), String(RadonString { value: \"116.203.225.48\" }), String(RadonString { value: \"167.172.39.205\" }), String(RadonString { value: \"159.69.75.174\" }), String(RadonString { value: \"46.4.217.205\" }), String(RadonString { value: \"135.181.56.50\" }), String(RadonString { value: \"168.119.172.143\" }), String(RadonString { value: \"136.244.77.174\" }), String(RadonString { value: \"116.203.46.135\" }), String(RadonString { value: \"85.114.132.62\" }), String(RadonString { value: \"148.251.128.18\" }), String(RadonString { value: \"148.251.152.217\" }), String(RadonString { value: \"136.243.64.186\" }), String(RadonString { value: \"159.89.7.193\" }), String(RadonString { value: \"88.99.104.178\" }), String(RadonString { value: \"206.166.251.188\" }), String(RadonString { value: \"178.63.62.52\" }), String(RadonString { value: \"136.244.77.174\" }), String(RadonString { value: \"116.202.48.46\" }), String(RadonString { value: \"209.97.138.224\" }), String(RadonString { value: \"95.217.181.71\" }), String(RadonString { value: \"49.12.73.196\" }), String(RadonString { value: \"213.239.207.48\" }), String(RadonString { value: \"206.189.63.220\" }), String(RadonString { value: \"116.202.48.46\" }), String(RadonString { value: \"116.203.46.135\" }), String(RadonString { value: \"195.201.63.43\" }), String(RadonString { value: \"138.201.62.73\" }), String(RadonString { value: \"88.99.218.237\" }), String(RadonString { value: \"116.202.131.22\" }), String(RadonString { value: \"46.4.75.56\" }), String(RadonString { value: \"88.99.104.178\" }), String(RadonString { value: \"168.119.58.62\" }), String(RadonString { value: \"78.46.84.92\" }), String(RadonString { value: \"159.69.74.217\" }), String(RadonString { value: \"213.133.101.58\" }), String(RadonString { value: \"178.63.67.40\" }), String(RadonString { value: \"78.46.86.104\" }), String(RadonString { value: \"207.154.254.153\" }), String(RadonString { value: \"116.202.235.36\" }), String(RadonString { value: \"168.119.4.50\" }), String(RadonString { value: \"167.99.243.125\" }), String(RadonString { value: \"167.172.39.205\" }), String(RadonString { value: \"195.201.206.107\" })], is_homogeneous: true }, max_count: 3 }") })
And this is the tally using the logic from WIP-0018:
Tally: RadonTypes::RadonError(UnhandledInterceptV2 { inner: None })
And by executing the tally stage locally it is possible to recover the inner
error:
Local tally: RadonTypes::RadonError(UnhandledInterceptV2 { inner: Some(ModeTie { values: RadonArray { value: [String(RadonString { value: "207.154.254.153" }), String(RadonString { value: "46.4.75.56" }), String(RadonString { value: "116.203.46.135" }), String(RadonString { value: "88.99.104.178" }), String(RadonString { value: "176.9.66.252" }), String(RadonString { value: "136.243.64.186" }), String(RadonString { value: "138.201.132.40" }), String(RadonString { value: "168.119.4.50" }), String(RadonString { value: "63.250.47.141" }), String(RadonString { value: "195.201.63.43" }), String(RadonString { value: "159.69.75.174" }), String(RadonString { value: "159.89.7.193" }), String(RadonString { value: "116.202.131.22" }), String(RadonString { value: "148.251.128.26" }), String(RadonString { value: "85.114.132.62" }), String(RadonString { value: "149.28.82.190" }), String(RadonString { value: "78.46.84.92" }), String(RadonString { value: "176.9.3.164" }), String(RadonString { value: "167.172.39.205" }), String(RadonString { value: "116.203.225.48" }), String(RadonString { value: "116.202.48.46" }), String(RadonString { value: "138.201.46.114" }), String(RadonString { value: "168.119.119.170" }), String(RadonString { value: "148.251.128.18" }), String(RadonString { value: "95.217.181.71" }), String(RadonString { value: "95.111.244.179" }), String(RadonString { value: "148.251.152.217" }), String(RadonString { value: "136.244.77.174" }), String(RadonString { value: "46.4.217.205" }), String(RadonString { value: "78.46.40.149" }), String(RadonString { value: "178.63.62.52" }), String(RadonString { value: "168.119.172.143" }), String(RadonString { value: "68.183.39.75" }), String(RadonString { value: "207.154.238.90" }), String(RadonString { value: "88.198.7.148" }), String(RadonString { value: "149.7.16.186" }), String(RadonString { value: "142.93.250.57" }), String(RadonString { value: "168.119.119.170" }), String(RadonString { value: "178.63.67.40" }), String(RadonString { value: "49.12.73.196" }), String(RadonString { value: "88.99.218.237" }), String(RadonString { value: "94.130.223.45" }), String(RadonString { value: "136.243.93.244" }), String(RadonString { value: "138.201.83.20" }), String(RadonString { value: "168.119.15.50" }), String(RadonString { value: "195.201.206.107" }), String(RadonString { value: "195.201.61.247" }), String(RadonString { value: "138.201.132.240" }), String(RadonString { value: "49.12.73.196" }), String(RadonString { value: "159.69.75.174" }), String(RadonString { value: "206.189.63.220" }), String(RadonString { value: "138.201.66.13" }), String(RadonString { value: "178.63.70.230" }), String(RadonString { value: "159.89.7.193" }), String(RadonString { value: "168.119.119.170" }), String(RadonString { value: "135.181.56.50" }), String(RadonString { value: "167.172.39.205" }), String(RadonString { value: "78.46.86.104" }), String(RadonString { value: "149.28.82.190" }), String(RadonString { value: "88.99.68.109" }), String(RadonString { value: "138.201.65.62" }), String(RadonString { value: "176.9.120.66" }), String(RadonString { value: "116.202.48.46" }), String(RadonString { value: "138.201.132.40" }), String(RadonString { value: "116.202.235.36" }), String(RadonString { value: "136.244.77.174" }), String(RadonString { value: "168.119.58.62" }), String(RadonString { value: "159.69.74.217" }), String(RadonString { value: "167.99.243.125" }), String(RadonString { value: "46.4.75.56" }), String(RadonString { value: "178.63.54.115" }), String(RadonString { value: "88.99.104.178" }), String(RadonString { value: "168.119.172.143" }), String(RadonString { value: "88.99.68.109" }), String(RadonString { value: "136.243.94.171" }), String(RadonString { value: "209.97.138.224" }), String(RadonString { value: "206.166.251.188" }), String(RadonString { value: "159.69.72.188" }), String(RadonString { value: "213.239.234.132" }), String(RadonString { value: "85.114.132.71" }), String(RadonString { value: "135.181.60.189" }), String(RadonString { value: "46.4.88.189" }), String(RadonString { value: "138.201.62.73" }), String(RadonString { value: "135.181.60.183" }), String(RadonString { value: "188.40.94.103" }), String(RadonString { value: "159.89.7.193" }), String(RadonString { value: "135.181.60.181" }), String(RadonString { value: "213.239.207.48" }), String(RadonString { value: "213.133.101.58" }), String(RadonString { value: "167.99.246.148" }), String(RadonString { value: "116.202.218.95" }), String(RadonString { value: "136.243.109.250" }), String(RadonString { value: "116.203.46.135" }), String(RadonString { value: "168.119.172.143" }), String(RadonString { value: "199.192.30.223" }), String(RadonString { value: "78.46.91.66" }), String(RadonString { value: "136.244.77.174" }), String(RadonString { value: "88.99.68.56" })], is_homogeneous: true }, max_count: 3 }) })
The other kind of UnhandledIntercept
can be seen in data request 463bb51d32942fb48edb88684a47440d8165bc97a1912acfab220f6de0e87ff5
. In this request, a majority of witnesses report an error, and the result of the request is a mode of the different errors, which happens to be an UnhandledIntercept
error. This is the tally using the old validation rules:
Tally: RadonTypes::RadonError(UnhandledIntercept { inner: None, message: Some("inner: Decode { from: \"cbor::value::Value\", to: \"RadonArray\" }") })
And this is the tally using the logic from WIP-0018:
Tally: RadonTypes::RadonError(UnhandledInterceptV2 { inner: None })
However, in this case, it is impossible to recover the original error because the error is created in the retrieval stage and we only have the removed error message from the tally result:
Local tally: RadonTypes::RadonError(UnhandledInterceptV2 { inner: None })
But in this case the reveals may contain some potentially helpful error message (same as before WIP-0018).
Starting from and including the activation epoch:
(1): All the data requests that result in an UnhandledIntercept
RADON error (error code 0xFF
) at the tally stage MUST serialize this error with no extra arguments.
(2): Any tally that contains an UnhandledIntercept
RADON error (error code 0xFF
) with arguments will be considered invalid.
(3): Any reveal that contains an UnhandledIntercept
RADON error (error code 0xFF
) MAY include an optional message argument.
- Implementer: a client that implements or adopts the protocol improvements proposed in this document.
- Non-implementer: a client that fails to or refuses to implement the protocol improvements proposed in this document.
Upon entry into force of the proposed improvements:
- Blocks and transactions that were considered valid by former versions of the protocol MUST continue to be considered valid.
- Blocks and transactions produced by non-implementers MUST be validated by implementers using the same rules as with those coming from implementers, that is, the new rules.
- Implementers MUST apply the proposed logic when evaluating transactions or computing their own data request eligibility.
As a result:
- Non-implementers MAY NOT get their blocks and transactions accepted by implementers.
- Implementers MAY NOT get their valid blocks accepted by non-implementers.
- Non-implementers MAY consolidate different blocks and superblocks than implementers.
Due to this last point, this MUST be considered a consensus-critical protocol improvement. An adoption plan MUST be proposed, setting an activation date that gives miners enough time in advance to upgrade their existing clients.
Most users of the RAD engine will be unaffected by this change, as it only changes the CBOR serialization of one error. For example, a client like Sheikah will not be affected because the data request result is encoded using JSON, so it can preserve the inner error in the UnhandledIntercept
error case. Any clients that want to see the error message that would have been included in a UnhandledIntercept
error can do so by fetching the reveals and running the tally script locally.
The solidity contracts that are using the [witnet-ethereum-bridge][witnet-ethereum-bridge] will not be affected because reading error 0xFF
returns a generic error message: Unknown error (0xFF)
, ignoring the message argument of the CBOR-encoded error.
A reference implementation for the proposed protocol improvement can be found in the unhandled-intercept-v2 branch of the witnet-rust repository.
An activation date is proposed for September 21 2021 at 9am UTC, that is, protocol epoch #656640.
From the perspective of TAPI, this proposal will be signaled along with the WIP-0017. The signaling bit is 1
(0b00000000000000000000000000000010
) and the first_signaling_epoch
is set to #656640.
This proposal has been cooperatively discussed and devised by many individuals from the Witnet development community.