diff --git a/.github/workflows/base_node_binaries.yml b/.github/workflows/base_node_binaries.yml index 080c3e8011..91e7fcef5f 100644 --- a/.github/workflows/base_node_binaries.yml +++ b/.github/workflows/base_node_binaries.yml @@ -17,7 +17,6 @@ on: env: TBN_FILENAME: "tari_base_node" -# PROTOC: protoc jobs: builds: @@ -95,7 +94,6 @@ jobs: libappindicator3-dev \ patchelf \ librsvg2-dev - # sudo apt-get -y upgrade - name: Install macOS dependencies if: startsWith(runner.os,'macOS') run: brew install cmake zip coreutils @@ -112,13 +110,14 @@ jobs: echo "SHARUN=shasum --algorithm 256" >> $GITHUB_ENV echo "CC=gcc" >> $GITHUB_ENV echo "TBN_EXT=" >> $GITHUB_ENV - echo "S3DESTDIR=linux" >> $GITHUB_ENV + echo "SHELL_EXT=.sh" >> $GITHUB_ENV + echo "PLATFORM_SPECIFIC_DIR=linux" >> $GITHUB_ENV echo "TBN_DIST=/dist" >> $GITHUB_ENV - name: Set environment variables - macOS if: startsWith(runner.os,'macOS') run: | - echo "S3DESTDIR=osx" >> $GITHUB_ENV + echo "PLATFORM_SPECIFIC_DIR=osx" >> $GITHUB_ENV - name: Set environment variables - Windows if: startsWith(runner.os,'Windows') @@ -126,14 +125,15 @@ jobs: run: | echo "SHARUN=pwsh C:\ProgramData\chocolatey\lib\psutils\tools\psutils-master\shasum.ps1 --algorithm 256" >> $GITHUB_ENV echo "TBN_EXT=.exe" >> $GITHUB_ENV + echo "SHELL_EXT=.bat" >> $GITHUB_ENV echo "TBN_DIST=\dist" >> $GITHUB_ENV - echo "S3DESTDIR=windows" >> $GITHUB_ENV + echo "PLATFORM_SPECIFIC_DIR=windows" >> $GITHUB_ENV echo "SQLITE3_LIB_DIR=C:\vcpkg\installed\x64-windows\lib" >> $GITHUB_ENV echo "OPENSSL_DIR=C:\Program Files\OpenSSL-Win64" >> $GITHUB_ENV echo "LIBCLANG_PATH=C:\Program Files\LLVM\bin" >> $GITHUB_ENV echo "C:\Strawberry\perl\bin" >> $GITHUB_PATH - - name: Caching + - name: Cache cargo files and outputs uses: actions/cache@v2 with: path: | @@ -142,50 +142,27 @@ jobs: target key: ${{ runner.os }}-${{ matrix.os }}-${{ matrix.target_cpu }}-${{ matrix.features }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - - name: Compile NPM + - name: Compile launchpad GUI run: | cd applications/launchpad/gui-vue npm install npm run build - - name: Compile react (collectibles) + - name: Compile collectibles GUI run: | cd applications/tari_collectibles/web-app npm install npm run build - - name: Build binaries + - name: Build rust binaries env: RUSTFLAGS: "-C target_cpu=${{ matrix.target_cpu }}" ROARING_ARCH: "${{ matrix.target_cpu }}" run: | echo "Cache Key: ${{ runner.os }}-${{ matrix.os }}-${{ matrix.target_cpu }}-${{ matrix.features }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}" - #cd applications/tari_base_node - #cargo build --release --bin tari_base_node --features ${{ matrix.features}} cargo build --release -# - name: Build tauri apps -# env: -# RUSTFLAGS: "-C target_cpu=${{ matrix.target_cpu }}" -# run: | -# echo "Cache Key: ${{ runner.os }}-${{ matrix.os }}-${{ matrix.target_cpu }}-${{ matrix.features }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}" -# cd applications/tari_collectibles -# npm install -# cd web-app -# npm install -# cd .. -# npm run tauri build - - # - name: Prepare Archives - # - name: Prepare Archives - # shell: bash - # run: | - # ls -la "$GITHUB_WORKSPACE" - # mkdir -p "$GITHUB_WORKSPACE${TBN_DIST}/archives" - # export distDir="$GITHUB_WORKSPACE${TBN_DIST}/archives" - # "$GITHUB_WORKSPACE/scripts/build_dists_tarball.sh" noBuild - - - name: Prepare binaries + - name: Copy binaries to folder for zipping shell: bash run: | mkdir -p "$GITHUB_WORKSPACE${TBN_DIST}" @@ -195,11 +172,9 @@ jobs: echo "Sha: ${VSHA_SHORT}" echo "VERSION=${VERSION}" >> $GITHUB_ENV echo "VSHA_SHORT=${VSHA_SHORT}" >> $GITHUB_ENV - #BINFILE="${TBN_FILENAME}-${VERSION}-${VSHA_SHORT}-${{ matrix.os }}-${{ matrix.target_cpu }}-${{ matrix.features }}${TBN_EXT}" BINFILE="${TBN_FILENAME}-${VERSION}-${VSHA_SHORT}-${{ matrix.os }}-${{ matrix.target_cpu }}${TBN_EXT}" echo "BINFILE=${BINFILE}" >> $GITHUB_ENV echo "Copying files for ${BINFILE} to $(pwd)" - #cp -v "$GITHUB_WORKSPACE/target/release/${TBN_FILENAME}${TBN_EXT}" "./${BINFILE}" ls -la "$GITHUB_WORKSPACE/target/release/" cp -v "$GITHUB_WORKSPACE/target/release/tari_base_node${TBN_EXT}" . cp -v "$GITHUB_WORKSPACE/target/release/tari_console_wallet${TBN_EXT}" . @@ -207,6 +182,8 @@ jobs: cp -v "$GITHUB_WORKSPACE/target/release/tari_mining_node${TBN_EXT}" . cp -v "$GITHUB_WORKSPACE/target/release/tari_validator_node${TBN_EXT}" . cp -v "$GITHUB_WORKSPACE/target/release/tari_collectibles${TBN_EXT}" . + cp -v "$GITHUB_WORKSPACE/target/release/tari_launchpad${TBN_EXT}" . + cp -v "$GITHUB_WORKSPACE/applications/tari_base_node/${PLATFORM_SPECIFIC_DIR}/runtime/start_tor${SHELL_EXT}" . - name: Build the macos pkg if: startsWith(runner.os,'macOS') @@ -310,7 +287,7 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} SOURCE: "${{ github.workspace }}${{ env.TBN_DIST }}" - DEST_DIR: "${{ env.S3DESTOVERRIDE }}${{ env.S3DESTDIR }}/" + DEST_DIR: "${{ env.S3DESTOVERRIDE }}${{ env.PLATFORM_SPECIFIC_DIR }}/" S3CMD: "cp" S3OPTIONS: '--recursive --exclude "*" --include "*.zip*"' # S3OPTIONS: '--recursive --exclude "*" --include "*.zip*"' @@ -346,7 +323,7 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} SOURCE: "${{ github.workspace }}${{ env.TBN_DIST }}" - DEST_DIR: "${{ env.S3DESTOVERRIDE }}${{ env.S3DESTDIR }}/" + DEST_DIR: "${{ env.S3DESTOVERRIDE }}${{ env.PLATFORM_SPECIFIC_DIR }}/" S3CMD: "cp" S3OPTIONS: '--recursive --exclude "*" --include "*.zip*"' # S3OPTIONS: '--recursive --exclude "*" --include "*.zip*"' diff --git a/README.md b/README.md index 613b76e6c6..9fcda7013d 100644 --- a/README.md +++ b/README.md @@ -234,7 +234,7 @@ The executables will either be inside your `~/tari/target/release` (on Linux) or directory, depending on the build choice above, and must be run from the command line. If the former build method was used, you can run it from that directory, or you more likely want to copy it somewhere more convenient. Make sure to start Tor service `~/tari/applications/tari_base_node/osx/start_tor` (on Mac), -`~/tari/applications/tari_base_node/ubuntu/start_tor` (on Linux) or +`~/tari/applications/tari_base_node/linux/start_tor` (on Linux) or `%USERPROFILE%\Code\tari\applications\tari_base_node\windows\start_tor.lnk` (on Windows). To run from any directory of your choice, where the executable is visible in the path (first time use): diff --git a/applications/tari_base_node/ubuntu/runtime/linux-diag-repo.sh b/applications/tari_base_node/linux/runtime/linux-diag-repo.sh old mode 100755 new mode 100644 similarity index 100% rename from applications/tari_base_node/ubuntu/runtime/linux-diag-repo.sh rename to applications/tari_base_node/linux/runtime/linux-diag-repo.sh diff --git a/applications/tari_base_node/ubuntu/runtime/setup_tor_service.sh b/applications/tari_base_node/linux/runtime/setup_tor_service.sh old mode 100755 new mode 100644 similarity index 100% rename from applications/tari_base_node/ubuntu/runtime/setup_tor_service.sh rename to applications/tari_base_node/linux/runtime/setup_tor_service.sh diff --git a/applications/tari_base_node/ubuntu/runtime/start_all.sh b/applications/tari_base_node/linux/runtime/start_all.sh old mode 100755 new mode 100644 similarity index 100% rename from applications/tari_base_node/ubuntu/runtime/start_all.sh rename to applications/tari_base_node/linux/runtime/start_all.sh diff --git a/applications/tari_base_node/ubuntu/runtime/start_tari_base_node.sh b/applications/tari_base_node/linux/runtime/start_tari_base_node.sh old mode 100755 new mode 100644 similarity index 100% rename from applications/tari_base_node/ubuntu/runtime/start_tari_base_node.sh rename to applications/tari_base_node/linux/runtime/start_tari_base_node.sh diff --git a/applications/tari_base_node/ubuntu/runtime/start_tor.sh b/applications/tari_base_node/linux/runtime/start_tor.sh old mode 100755 new mode 100644 similarity index 86% rename from applications/tari_base_node/ubuntu/runtime/start_tor.sh rename to applications/tari_base_node/linux/runtime/start_tor.sh index 81be8bbe1f..b63cf53a95 --- a/applications/tari_base_node/ubuntu/runtime/start_tor.sh +++ b/applications/tari_base_node/linux/runtime/start_tor.sh @@ -14,9 +14,9 @@ fi #gnome-terminal --working-directory="$PWD" -- tor --allow-missing-torrc --ignore-missing-torrc \ # --clientonly 1 --socksport 9050 --controlport 127.0.0.1:9051 \ -# --log "notice stdout" --clientuseipv6 1 "${no_output}" +# --log "warn stdout" --clientuseipv6 1 "${no_output}" gnome-terminal --working-directory="$PWD" -- tor --allow-missing-torrc --ignore-missing-torrc \ --clientonly 1 --socksport 9050 --controlport 127.0.0.1:9051 \ - --log "notice stdout" --clientuseipv6 1 + --log "warn stdout" --clientuseipv6 1 diff --git a/applications/tari_base_node/linux/setup_tor_service b/applications/tari_base_node/linux/setup_tor_service new file mode 100644 index 0000000000..b4c2ad40dc --- /dev/null +++ b/applications/tari_base_node/linux/setup_tor_service @@ -0,0 +1 @@ +./runtime/setup_tor_service.sh \ No newline at end of file diff --git a/applications/tari_base_node/linux/start_all b/applications/tari_base_node/linux/start_all new file mode 100644 index 0000000000..99f1754e18 --- /dev/null +++ b/applications/tari_base_node/linux/start_all @@ -0,0 +1 @@ +./runtime/start_all.sh \ No newline at end of file diff --git a/applications/tari_base_node/linux/start_tari_base_node b/applications/tari_base_node/linux/start_tari_base_node new file mode 100644 index 0000000000..f192df73da --- /dev/null +++ b/applications/tari_base_node/linux/start_tari_base_node @@ -0,0 +1 @@ +./runtime/start_tari_base_node.sh \ No newline at end of file diff --git a/applications/tari_base_node/linux/start_tor b/applications/tari_base_node/linux/start_tor new file mode 100644 index 0000000000..1997cc7ff2 --- /dev/null +++ b/applications/tari_base_node/linux/start_tor @@ -0,0 +1 @@ +./runtime/start_tor.sh \ No newline at end of file diff --git a/applications/tari_base_node/ubuntu/supervisord.md b/applications/tari_base_node/linux/supervisord.md similarity index 100% rename from applications/tari_base_node/ubuntu/supervisord.md rename to applications/tari_base_node/linux/supervisord.md diff --git a/applications/tari_base_node/osx/runtime/start_tor.sh b/applications/tari_base_node/osx/runtime/start_tor.sh index ef9cf45734..ec6e393929 100755 --- a/applications/tari_base_node/osx/runtime/start_tor.sh +++ b/applications/tari_base_node/osx/runtime/start_tor.sh @@ -5,4 +5,4 @@ TOR=$(which tor) $TOR --allow-missing-torrc --ignore-missing-torrc \ --clientonly 1 --socksport 9050 --controlport 127.0.0.1:9051 \ - --log "notice stdout" --clientuseipv6 1 + --log "warn stdout" --clientuseipv6 1 diff --git a/applications/tari_base_node/src/main.rs b/applications/tari_base_node/src/main.rs index 3b1b70b541..3150bdc6a6 100644 --- a/applications/tari_base_node/src/main.rs +++ b/applications/tari_base_node/src/main.rs @@ -49,7 +49,7 @@ /// ``` /// tor --allow-missing-torrc --ignore-missing-torrc \ /// --clientonly 1 --socksport 9050 --controlport 127.0.0.1:9051 \ -/// --log "notice stdout" --clientuseipv6 1 +/// --log "warn stdout" --clientuseipv6 1 /// ``` /// /// For the first run diff --git a/applications/tari_base_node/ubuntu/setup_tor_service b/applications/tari_base_node/ubuntu/setup_tor_service deleted file mode 120000 index b4c2ad40dc..0000000000 --- a/applications/tari_base_node/ubuntu/setup_tor_service +++ /dev/null @@ -1 +0,0 @@ -./runtime/setup_tor_service.sh \ No newline at end of file diff --git a/applications/tari_base_node/ubuntu/start_all b/applications/tari_base_node/ubuntu/start_all deleted file mode 120000 index 99f1754e18..0000000000 --- a/applications/tari_base_node/ubuntu/start_all +++ /dev/null @@ -1 +0,0 @@ -./runtime/start_all.sh \ No newline at end of file diff --git a/applications/tari_base_node/ubuntu/start_tari_base_node b/applications/tari_base_node/ubuntu/start_tari_base_node deleted file mode 120000 index f192df73da..0000000000 --- a/applications/tari_base_node/ubuntu/start_tari_base_node +++ /dev/null @@ -1 +0,0 @@ -./runtime/start_tari_base_node.sh \ No newline at end of file diff --git a/applications/tari_base_node/ubuntu/start_tor b/applications/tari_base_node/ubuntu/start_tor deleted file mode 120000 index 1997cc7ff2..0000000000 --- a/applications/tari_base_node/ubuntu/start_tor +++ /dev/null @@ -1 +0,0 @@ -./runtime/start_tor.sh \ No newline at end of file diff --git a/applications/tari_base_node/windows/runtime/start_tor.bat b/applications/tari_base_node/windows/runtime/start_tor.bat index 234bace449..424d28ccc2 100644 --- a/applications/tari_base_node/windows/runtime/start_tor.bat +++ b/applications/tari_base_node/windows/runtime/start_tor.bat @@ -31,7 +31,7 @@ if not defined TOR_RUNNING ( pause ) ) - start tor --allow-missing-torrc --ignore-missing-torrc --clientonly 1 --socksport 9050 --controlport 127.0.0.1:9051 --clientuseipv6 1 --log "notice stdout" + start tor --allow-missing-torrc --ignore-missing-torrc --clientonly 1 --socksport 9050 --controlport 127.0.0.1:9051 --clientuseipv6 1 --log "warn stdout" echo Attempting to start Tor service on ports 9050 and 9051 ping -n 15 localhost>nul ) else ( diff --git a/applications/tari_console_wallet/ubuntu/runtime/start_tari_console_wallet.sh b/applications/tari_console_wallet/linux/runtime/start_tari_console_wallet.sh old mode 100755 new mode 100644 similarity index 100% rename from applications/tari_console_wallet/ubuntu/runtime/start_tari_console_wallet.sh rename to applications/tari_console_wallet/linux/runtime/start_tari_console_wallet.sh diff --git a/applications/tari_console_wallet/linux/start_tari_console_wallet b/applications/tari_console_wallet/linux/start_tari_console_wallet new file mode 100644 index 0000000000..b49ab10859 --- /dev/null +++ b/applications/tari_console_wallet/linux/start_tari_console_wallet @@ -0,0 +1 @@ +./runtime/start_tari_console_wallet.sh \ No newline at end of file diff --git a/applications/tari_console_wallet/ubuntu/start_tari_console_wallet b/applications/tari_console_wallet/ubuntu/start_tari_console_wallet deleted file mode 120000 index b49ab10859..0000000000 --- a/applications/tari_console_wallet/ubuntu/start_tari_console_wallet +++ /dev/null @@ -1 +0,0 @@ -./runtime/start_tari_console_wallet.sh \ No newline at end of file diff --git a/base_layer/core/src/consensus/consensus_constants.rs b/base_layer/core/src/consensus/consensus_constants.rs index 229896311e..cc3ba0a93b 100644 --- a/base_layer/core/src/consensus/consensus_constants.rs +++ b/base_layer/core/src/consensus/consensus_constants.rs @@ -27,7 +27,7 @@ use tari_common::configuration::Network; use tari_crypto::{script, tari_utilities::epoch_time::EpochTime}; use crate::{ - consensus::{network::NetworkConsensus, ConsensusEncodingSized, ConsensusEncodingWrapper}, + consensus::{network::NetworkConsensus, ConsensusEncodingSized}, proof_of_work::{Difficulty, PowAlgorithm}, transactions::{ tari_amount::{uT, MicroTari, T}, @@ -143,8 +143,7 @@ impl ConsensusConstants { pub fn coinbase_weight(&self) -> u64 { // TODO: We do not know what script, features etc a coinbase has - this should be max coinbase size? let metadata_size = self.transaction_weight.round_up_metadata_size( - ConsensusEncodingWrapper::wrap(&script![Nop]).consensus_encode_exact_size() + - OutputFeatures::default().consensus_encode_exact_size(), + script![Nop].consensus_encode_exact_size() + OutputFeatures::default().consensus_encode_exact_size(), ); self.transaction_weight.calculate(1, 0, 1, metadata_size) } diff --git a/base_layer/core/src/consensus/consensus_encoding.rs b/base_layer/core/src/consensus/consensus_encoding.rs index 6094d2c3c4..26dd2b2b68 100644 --- a/base_layer/core/src/consensus/consensus_encoding.rs +++ b/base_layer/core/src/consensus/consensus_encoding.rs @@ -24,8 +24,8 @@ use std::io; /// Abstracts the ability of a type to canonically encode itself for the purposes of consensus pub trait ConsensusEncoding { - /// Encode to the given writer returning the number of bytes writter. - /// If writing to this Writer is infallible, this implementation must always succeed. + /// Encode to the given writer returning the number of bytes written. + /// If writing to this Writer is infallible, this implementation MUST always succeed. fn consensus_encode(&self, writer: &mut W) -> Result; } @@ -41,33 +41,81 @@ pub trait ConsensusDecoding: Sized { fn consensus_decode(reader: &mut R) -> Result; } -pub struct ConsensusEncodingWrapper<'a, T> { - inner: &'a T, +pub trait ToConsensusBytes { + fn to_consensus_bytes(&self) -> Vec; } -impl<'a, T> ConsensusEncodingWrapper<'a, T> { - pub fn wrap(inner: &'a T) -> Self { - Self { inner } +impl ToConsensusBytes for T { + fn to_consensus_bytes(&self) -> Vec { + let mut buf = Vec::with_capacity(self.consensus_encode_exact_size()); + // Vec's write impl is infallible, as per the ConsensusEncoding contract, consensus_encode is infallible + self.consensus_encode(&mut buf).expect("unreachable panic"); + buf } } -// TODO: move traits and implement consensus encoding for TariScript -// for now, this wrapper will do that job -mod tariscript_impl { +impl ConsensusEncoding for Vec { + fn consensus_encode(&self, writer: &mut W) -> Result { + writer.write(self) + } +} + +impl ConsensusEncodingSized for Vec { + fn consensus_encode_exact_size(&self) -> usize { + self.len() + } +} + +macro_rules! consensus_encoding_varint_impl { + ($ty:ty) => { + impl ConsensusEncoding for $ty { + fn consensus_encode(&self, writer: &mut W) -> Result { + use integer_encoding::VarIntWriter; + let bytes_written = writer.write_varint(*self)?; + Ok(bytes_written) + } + } + + impl ConsensusDecoding for $ty { + fn consensus_decode(reader: &mut R) -> Result { + use integer_encoding::VarIntReader; + let value = reader.read_varint()?; + Ok(value) + } + } + + impl ConsensusEncodingSized for $ty { + fn consensus_encode_exact_size(&self) -> usize { + use integer_encoding::VarInt; + self.required_space() + } + } + }; +} + +consensus_encoding_varint_impl!(u8); +consensus_encoding_varint_impl!(u64); + +// Keep separate the dependencies of the impls that may in future be implemented in tari crypto +mod impls { + use std::io::Read; + + use tari_common_types::types::{Commitment, PrivateKey, PublicKey, Signature}; use tari_crypto::script::TariScript; + use tari_utilities::ByteArray; use super::*; use crate::common::byte_counter::ByteCounter; - impl<'a> ConsensusEncoding for ConsensusEncodingWrapper<'a, TariScript> { + //---------------------------------- TariScript --------------------------------------------// + + impl ConsensusEncoding for TariScript { fn consensus_encode(&self, writer: &mut W) -> Result { - let bytes = self.inner.as_bytes(); - writer.write_all(&bytes)?; - Ok(bytes.len()) + self.as_bytes().consensus_encode(writer) } } - impl<'a> ConsensusEncodingSized for ConsensusEncodingWrapper<'a, TariScript> { + impl ConsensusEncodingSized for TariScript { fn consensus_encode_exact_size(&self) -> usize { let mut counter = ByteCounter::new(); // TODO: consensus_encode_exact_size must be cheap to run @@ -76,4 +124,92 @@ mod tariscript_impl { counter.get() } } + + //---------------------------------- PublicKey --------------------------------------------// + + impl ConsensusEncoding for PublicKey { + fn consensus_encode(&self, writer: &mut W) -> Result { + writer.write(self.as_bytes()) + } + } + + impl ConsensusEncodingSized for PublicKey { + fn consensus_encode_exact_size(&self) -> usize { + let mut counter = ByteCounter::new(); + // TODO: consensus_encode_exact_size must be cheap to run + // unreachable panic: ByteCounter is infallible + self.consensus_encode(&mut counter).expect("unreachable"); + counter.get() + } + } + + impl ConsensusDecoding for PublicKey { + fn consensus_decode(reader: &mut R) -> Result { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf)?; + let pk = PublicKey::from_bytes(&buf[..]).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?; + Ok(pk) + } + } + + //---------------------------------- Commitment --------------------------------------------// + + impl ConsensusEncoding for Commitment { + fn consensus_encode(&self, writer: &mut W) -> Result { + let buf = self.as_bytes(); + let len = buf.len(); + writer.write_all(buf)?; + Ok(len) + } + } + + impl ConsensusEncodingSized for Commitment { + fn consensus_encode_exact_size(&self) -> usize { + 32 + } + } + + impl ConsensusDecoding for Commitment { + fn consensus_decode(reader: &mut R) -> Result { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf)?; + let commitment = + Commitment::from_bytes(&buf[..]).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?; + Ok(commitment) + } + } + + //---------------------------------- Signature --------------------------------------------// + + impl ConsensusEncoding for Signature { + fn consensus_encode(&self, writer: &mut W) -> Result { + let pub_nonce = self.get_public_nonce().as_bytes(); + let mut written = pub_nonce.len(); + writer.write_all(pub_nonce)?; + let sig = self.get_signature().as_bytes(); + written += sig.len(); + writer.write_all(sig)?; + Ok(written) + } + } + + impl ConsensusEncodingSized for Signature { + fn consensus_encode_exact_size(&self) -> usize { + 96 + } + } + + impl ConsensusDecoding for Signature { + fn consensus_decode(reader: &mut R) -> Result { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf)?; + let pub_nonce = + PublicKey::from_bytes(&buf[..]).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?; + let mut buf = [0u8; 64]; + reader.read_exact(&mut buf)?; + let sig = + PrivateKey::from_bytes(&buf[..]).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?; + Ok(Signature::new(pub_nonce, sig)) + } + } } diff --git a/base_layer/core/src/consensus/mod.rs b/base_layer/core/src/consensus/mod.rs index e1a2e95e84..dbed7dca39 100644 --- a/base_layer/core/src/consensus/mod.rs +++ b/base_layer/core/src/consensus/mod.rs @@ -30,7 +30,7 @@ mod consensus_manager; pub use consensus_manager::{ConsensusManager, ConsensusManagerBuilder, ConsensusManagerError}; mod consensus_encoding; -pub use consensus_encoding::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, ConsensusEncodingWrapper}; +pub use consensus_encoding::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, ToConsensusBytes}; mod network; pub use network::NetworkConsensus; diff --git a/base_layer/core/src/covenants/arguments.rs b/base_layer/core/src/covenants/arguments.rs new file mode 100644 index 0000000000..fd664dd5d3 --- /dev/null +++ b/base_layer/core/src/covenants/arguments.rs @@ -0,0 +1,310 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ + fmt::{Display, Formatter}, + io, +}; + +use integer_encoding::{VarIntReader, VarIntWriter}; +use tari_common_types::types::{Commitment, PublicKey}; +use tari_crypto::script::TariScript; +use tari_utilities::hex::{to_hex, Hex}; + +use crate::{ + consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized}, + covenants::{ + byte_codes, + covenant::Covenant, + decoder::{CovenantDecodeError, CovenentReadExt}, + encoder::CovenentWriteExt, + error::CovenantError, + fields::{OutputField, OutputFields}, + }, +}; + +const MAX_TARISCRIPT_ARG_SIZE: usize = 4096; +const MAX_COVENANT_ARG_SIZE: usize = 4096; +const MAX_BYTES_ARG_SIZE: usize = 4096; + +pub type Hash = [u8; 32]; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CovenantArg { + Hash(Hash), + PublicKey(PublicKey), + Commitment(Commitment), + TariScript(TariScript), + Covenant(Covenant), + Uint(u64), + OutputField(OutputField), + OutputFields(OutputFields), + Bytes(Vec), +} + +impl CovenantArg { + pub fn is_valid_code(code: u8) -> bool { + byte_codes::is_valid_arg_code(code) + } + + pub fn read_from(reader: &mut R, code: u8) -> Result { + use byte_codes::*; + match code { + ARG_HASH => { + let mut hash = [0u8; 32]; + reader.read_exact(&mut hash)?; + Ok(CovenantArg::Hash(hash)) + }, + ARG_PUBLIC_KEY => { + let pk = PublicKey::consensus_decode(reader)?; + Ok(CovenantArg::PublicKey(pk)) + }, + ARG_COMMITMENT => Ok(CovenantArg::Commitment(Commitment::consensus_decode(reader)?)), + ARG_TARI_SCRIPT => { + let buf = reader.read_variable_length_bytes(MAX_TARISCRIPT_ARG_SIZE)?; + let script = TariScript::from_bytes(&buf[..])?; + Ok(CovenantArg::TariScript(script)) + }, + ARG_COVENANT => { + let buf = reader.read_variable_length_bytes(MAX_COVENANT_ARG_SIZE)?; + let covenant = Covenant::consensus_decode(&mut buf.as_slice())?; + Ok(CovenantArg::Covenant(covenant)) + }, + ARG_UINT => { + let v = reader.read_varint::()?; + Ok(CovenantArg::Uint(v)) + }, + ARG_OUTPUT_FIELD => { + let v = reader + .read_next_byte_code()? + .ok_or(CovenantDecodeError::UnexpectedEof { + expected: "Output field byte code", + })?; + let field = OutputField::from_byte(v)?; + Ok(CovenantArg::OutputField(field)) + }, + ARG_OUTPUT_FIELDS => { + // Each field code is a byte + let fields = OutputFields::read_from(reader)?; + Ok(CovenantArg::OutputFields(fields)) + }, + ARG_BYTES => { + let buf = reader.read_variable_length_bytes(MAX_BYTES_ARG_SIZE)?; + Ok(CovenantArg::Bytes(buf)) + }, + + _ => Err(CovenantDecodeError::UnknownArgByteCode { code }), + } + } + + pub fn write_to(&self, writer: &mut W) -> Result { + use byte_codes::*; + use CovenantArg::*; + + let mut written = 0; + match self { + Hash(hash) => { + written += writer.write_u8_fixed(ARG_HASH)?; + written += hash.len(); + writer.write_all(&hash[..])?; + }, + PublicKey(pk) => { + written += writer.write_u8_fixed(ARG_PUBLIC_KEY)?; + written += pk.consensus_encode(writer)?; + }, + Commitment(commitment) => { + written += writer.write_u8_fixed(ARG_COMMITMENT)?; + written += commitment.consensus_encode(writer)?; + }, + TariScript(script) => { + written += writer.write_u8_fixed(ARG_TARI_SCRIPT)?; + written += writer.write_variable_length_bytes(&script.as_bytes())?; + }, + Covenant(covenant) => { + written += writer.write_u8_fixed(ARG_COVENANT)?; + let len = covenant.consensus_encode_exact_size(); + written += writer.write_varint(len)?; + written += covenant.write_to(writer)?; + }, + Uint(int) => { + written += writer.write_u8_fixed(ARG_UINT)?; + written += writer.write_varint(*int)?; + }, + OutputField(field) => { + written += writer.write_u8_fixed(ARG_OUTPUT_FIELD)?; + written += writer.write_varint(field.as_byte())?; + }, + OutputFields(fields) => { + written += writer.write_u8_fixed(ARG_OUTPUT_FIELDS)?; + written += fields.write_to(writer)?; + }, + Bytes(bytes) => { + written += writer.write_u8_fixed(ARG_BYTES)?; + written += writer.write_variable_length_bytes(bytes)?; + }, + } + + Ok(written) + } +} + +macro_rules! require_x_impl { + ($name:ident, $output:ident, $expected: expr) => { + #[allow(dead_code)] + pub(super) fn $name(self) -> Result<$output, CovenantError> { + match self { + CovenantArg::$output(obj) => Ok(obj), + got => Err(CovenantError::UnexpectedArgument { + expected: $expected, + got: got.to_string(), + }), + } + } + }; +} + +impl CovenantArg { + require_x_impl!(require_hash, Hash, "hash"); + + require_x_impl!(require_publickey, PublicKey, "publickey"); + + require_x_impl!(require_commitment, Commitment, "commitment"); + + require_x_impl!(require_tariscript, TariScript, "script"); + + require_x_impl!(require_covenant, Covenant, "covenant"); + + require_x_impl!(require_outputfield, OutputField, "outputfield"); + + require_x_impl!(require_outputfields, OutputFields, "outputfields"); + + pub fn require_bytes(self) -> Result, CovenantError> { + match self { + CovenantArg::Bytes(val) => Ok(val), + got => Err(CovenantError::UnexpectedArgument { + expected: "bytes", + got: got.to_string(), + }), + } + } + + pub fn require_uint(self) -> Result { + match self { + CovenantArg::Uint(val) => Ok(val), + got => Err(CovenantError::UnexpectedArgument { + expected: "uint", + got: got.to_string(), + }), + } + } +} + +impl Display for CovenantArg { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use CovenantArg::*; + match self { + Hash(hash) => write!(f, "Hash({})", to_hex(&hash[..])), + PublicKey(public_key) => write!(f, "PublicKey({})", public_key.to_hex()), + Commitment(commitment) => write!(f, "Commitment({})", commitment.to_hex()), + TariScript(_) => write!(f, "TariScript(...)"), + Covenant(_) => write!(f, "Covenant(...)"), + Uint(v) => write!(f, "Uint({})", v), + OutputField(field) => write!(f, "OutputField({})", field.as_byte()), + OutputFields(fields) => write!(f, "OutputFields({} field(s))", fields.len()), + Bytes(bytes) => write!(f, "Bytes({} byte(s))", bytes.len()), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + mod require_x_impl { + use super::*; + + #[test] + fn test() { + // This is mostly to remove unused function warnings + let arg = CovenantArg::Uint(123); + arg.clone().require_bytes().unwrap_err(); + let v = arg.clone().require_uint().unwrap(); + assert_eq!(v, 123); + arg.clone().require_hash().unwrap_err(); + arg.clone().require_outputfield().unwrap_err(); + arg.clone().require_covenant().unwrap_err(); + arg.clone().require_commitment().unwrap_err(); + arg.clone().require_outputfields().unwrap_err(); + arg.clone().require_publickey().unwrap_err(); + arg.require_tariscript().unwrap_err(); + } + } + + mod write_to { + use tari_common_types::types::Commitment; + use tari_crypto::script; + use tari_utilities::hex::from_hex; + + use super::*; + use crate::{covenant, covenants::byte_codes::*}; + + fn test_case(arg: CovenantArg, expected: &[u8]) { + let mut buf = Vec::new(); + arg.write_to(&mut buf).unwrap(); + assert_eq!(buf, expected); + } + + #[test] + fn test() { + test_case(CovenantArg::Uint(2048), &[ARG_UINT, 0x80, 0x10][..]); + test_case( + CovenantArg::Covenant(covenant!(identity())), + &[ARG_COVENANT, 0x01, 0x20][..], + ); + test_case( + CovenantArg::Bytes(vec![0x01, 0x02, 0xaa]), + &[ARG_BYTES, 0x03, 0x01, 0x02, 0xaa][..], + ); + test_case( + CovenantArg::Commitment(Commitment::default()), + &from_hex("030000000000000000000000000000000000000000000000000000000000000000").unwrap(), + ); + test_case( + CovenantArg::PublicKey(PublicKey::default()), + &from_hex("020000000000000000000000000000000000000000000000000000000000000000").unwrap(), + ); + test_case( + CovenantArg::Hash([0u8; 32]), + &from_hex("010000000000000000000000000000000000000000000000000000000000000000").unwrap(), + ); + test_case(CovenantArg::TariScript(script!(Nop)), &[ARG_TARI_SCRIPT, 0x01, 0x73]); + test_case(CovenantArg::OutputField(OutputField::Covenant), &[ + ARG_OUTPUT_FIELD, + FIELD_COVENANT, + ]); + test_case( + CovenantArg::OutputFields(OutputFields::from(vec![OutputField::Features, OutputField::Commitment])), + &[ARG_OUTPUT_FIELDS, 0x02, FIELD_FEATURES, FIELD_COMMITMENT], + ); + } + } +} diff --git a/base_layer/core/src/covenants/byte_codes.rs b/base_layer/core/src/covenants/byte_codes.rs new file mode 100644 index 0000000000..d8e1ee910a --- /dev/null +++ b/base_layer/core/src/covenants/byte_codes.rs @@ -0,0 +1,65 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//---------------------------------- ARG byte codes --------------------------------------------// +pub(super) fn is_valid_arg_code(code: u8) -> bool { + (0x01..=0x09).contains(&code) +} +pub const ARG_HASH: u8 = 0x01; +pub const ARG_PUBLIC_KEY: u8 = 0x02; +pub const ARG_COMMITMENT: u8 = 0x03; +pub const ARG_TARI_SCRIPT: u8 = 0x04; +pub const ARG_COVENANT: u8 = 0x05; +pub const ARG_UINT: u8 = 0x06; +pub const ARG_OUTPUT_FIELD: u8 = 0x07; +pub const ARG_OUTPUT_FIELDS: u8 = 0x08; +pub const ARG_BYTES: u8 = 0x09; + +//---------------------------------- FILTER byte codes --------------------------------------------// + +pub(super) fn is_valid_filter_code(code: u8) -> bool { + (0x20..=0x24).contains(&code) || (0x30..=0x34).contains(&code) +} + +pub const FILTER_IDENTITY: u8 = 0x20; +pub const FILTER_AND: u8 = 0x21; +pub const FILTER_OR: u8 = 0x22; +pub const FILTER_XOR: u8 = 0x23; +pub const FILTER_NOT: u8 = 0x24; + +pub const FILTER_OUTPUT_HASH_EQ: u8 = 0x30; +pub const FILTER_FIELDS_PRESERVED: u8 = 0x31; +pub const FILTER_FIELDS_HASHED_EQ: u8 = 0x32; +pub const FILTER_FIELD_EQ: u8 = 0x33; +pub const FILTER_ABSOLUTE_HEIGHT: u8 = 0x34; + +//---------------------------------- FIELD byte codes --------------------------------------------// +pub const FIELD_COMMITMENT: u8 = 0x00; +pub const FIELD_SCRIPT: u8 = 0x01; +pub const FIELD_SENDER_OFFSET_PUBLIC_KEY: u8 = 0x02; +pub const FIELD_COVENANT: u8 = 0x03; +pub const FIELD_FEATURES: u8 = 0x04; +pub const FIELD_FEATURES_FLAGS: u8 = 0x05; +pub const FIELD_FEATURES_MATURITY: u8 = 0x06; +pub const FIELD_FEATURES_UNIQUE_ID: u8 = 0x07; +pub const FIELD_FEATURES_PARENT_PUBLIC_KEY: u8 = 0x08; +pub const FIELD_FEATURES_METADATA: u8 = 0x09; diff --git a/base_layer/core/src/covenants/context.rs b/base_layer/core/src/covenants/context.rs new file mode 100644 index 0000000000..c3a17b3df0 --- /dev/null +++ b/base_layer/core/src/covenants/context.rs @@ -0,0 +1,82 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{ + covenants::{ + arguments::CovenantArg, + error::CovenantError, + filters::CovenantFilter, + token::{CovenantToken, CovenantTokenCollection}, + }, + transactions::transaction::TransactionInput, +}; + +pub struct CovenantContext<'a> { + input: &'a TransactionInput, + tokens: CovenantTokenCollection, + block_height: u64, +} + +impl<'a> CovenantContext<'a> { + pub fn new(tokens: CovenantTokenCollection, input: &'a TransactionInput, block_height: u64) -> Self { + Self { + input, + tokens, + block_height, + } + } + + pub fn has_more_tokens(&self) -> bool { + !self.tokens.is_empty() + } + + pub fn next_arg(&mut self) -> Result { + match self.tokens.next().ok_or(CovenantError::UnexpectedEndOfTokens)? { + CovenantToken::Arg(arg) => Ok(arg), + CovenantToken::Filter(_) => Err(CovenantError::ExpectedArgButGotFilter), + } + } + + // Only happens to be used in tests for now + #[cfg(test)] + pub fn next_filter(&mut self) -> Option { + match self.tokens.next()? { + CovenantToken::Filter(filter) => Some(filter), + CovenantToken::Arg(_) => None, + } + } + + pub fn require_next_filter(&mut self) -> Result { + match self.tokens.next().ok_or(CovenantError::UnexpectedEndOfTokens)? { + CovenantToken::Filter(filter) => Ok(filter), + CovenantToken::Arg(_) => Err(CovenantError::ExpectedFilterButGotArg), + } + } + + pub fn block_height(&self) -> u64 { + self.block_height + } + + pub fn input(&self) -> &TransactionInput { + self.input + } +} diff --git a/base_layer/core/src/covenants/covenant.rs b/base_layer/core/src/covenants/covenant.rs new file mode 100644 index 0000000000..990721aa86 --- /dev/null +++ b/base_layer/core/src/covenants/covenant.rs @@ -0,0 +1,155 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{io, iter::FromIterator}; + +use crate::{ + common::byte_counter::ByteCounter, + consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized}, + covenants::{ + context::CovenantContext, + decoder::{CovenantDecodeError, CovenantTokenDecoder}, + encoder::CovenantTokenEncoder, + error::CovenantError, + filters::Filter, + output_set::OutputSet, + token::{CovenantToken, CovenantTokenCollection}, + }, + transactions::transaction::{TransactionInput, TransactionOutput}, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct Covenant { + tokens: Vec, +} + +impl Covenant { + pub fn new() -> Self { + Self { tokens: Vec::new() } + } + + pub fn from_bytes(mut bytes: &[u8]) -> Result { + if bytes.is_empty() { + return Ok(Self::new()); + } + CovenantTokenDecoder::new(&mut bytes).collect() + } + + pub(super) fn write_to(&self, writer: &mut W) -> Result { + CovenantTokenEncoder::new(self.tokens.as_slice()).write_to(writer) + } + + pub fn execute<'a>( + &self, + block_height: u64, + input: &TransactionInput, + outputs: &'a [TransactionOutput], + ) -> Result { + if self.tokens.is_empty() { + // Empty covenants always pass + return Ok(outputs.len()); + } + + let tokens = CovenantTokenCollection::from_iter(self.tokens.clone()); + let mut cx = CovenantContext::new(tokens, input, block_height); + let root = cx.require_next_filter()?; + let mut output_set = OutputSet::new(outputs); + root.filter(&mut cx, &mut output_set)?; + if cx.has_more_tokens() { + return Err(CovenantError::RemainingTokens); + } + if output_set.is_empty() { + return Err(CovenantError::NoMatchingOutputs); + } + + Ok(output_set.len()) + } + + pub fn push_token(&mut self, token: CovenantToken) { + self.tokens.push(token); + } + + #[cfg(test)] + pub(super) fn tokens(&self) -> &[CovenantToken] { + &self.tokens + } +} + +impl ConsensusEncoding for Covenant { + fn consensus_encode(&self, writer: &mut W) -> Result { + self.write_to(writer) + } +} + +impl ConsensusEncodingSized for Covenant { + fn consensus_encode_exact_size(&self) -> usize { + let mut byte_counter = ByteCounter::new(); + self.write_to(&mut byte_counter).expect("unreachable panic"); + byte_counter.get() + } +} + +impl ConsensusDecoding for Covenant { + fn consensus_decode(reader: &mut R) -> Result { + CovenantTokenDecoder::new(reader) + .collect::>() + .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err)) + } +} + +impl FromIterator for Covenant { + fn from_iter>(iter: T) -> Self { + Self { + tokens: iter.into_iter().collect(), + } + } +} + +#[cfg(test)] +mod test { + use crate::{ + covenant, + covenants::test::{create_input, create_outputs}, + }; + + #[test] + fn it_succeeds_when_empty() { + let outputs = create_outputs(10, Default::default()); + let input = create_input(); + let covenant = covenant!(); + let num_matching_outputs = covenant.execute(0, &input, &outputs).unwrap(); + assert_eq!(num_matching_outputs, 10); + } + + #[test] + fn it_executes_the_covenant() { + let mut outputs = create_outputs(10, Default::default()); + outputs[4].features.maturity = 42; + outputs[5].features.maturity = 42; + outputs[7].features.maturity = 42; + let mut input = create_input(); + input.set_maturity(42).unwrap(); + let covenant = covenant!(fields_preserved(@fields(@field::features))); + let num_matching_outputs = covenant.execute(0, &input, &outputs).unwrap(); + assert_eq!(num_matching_outputs, 3); + } +} diff --git a/base_layer/core/src/covenants/decoder.rs b/base_layer/core/src/covenants/decoder.rs new file mode 100644 index 0000000000..3ff465796e --- /dev/null +++ b/base_layer/core/src/covenants/decoder.rs @@ -0,0 +1,167 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::io; + +use integer_encoding::VarIntReader; +use tari_crypto::script::ScriptError; + +use crate::covenants::token::CovenantToken; + +pub struct CovenantTokenDecoder<'a, R> { + buf: &'a mut R, + is_complete: bool, +} + +impl<'a, R: io::Read> CovenantTokenDecoder<'a, R> { + pub fn new(buf: &'a mut R) -> Self { + Self { + buf, + is_complete: false, + } + } +} + +impl Iterator for CovenantTokenDecoder<'_, R> { + type Item = Result; + + fn next(&mut self) -> Option { + if self.is_complete { + return None; + } + + match CovenantToken::read_from(self.buf) { + Ok(Some(token)) => Some(Ok(token)), + Ok(None) => { + self.is_complete = true; + None + }, + Err(err) => { + self.is_complete = true; + Some(Err(err)) + }, + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum CovenantDecodeError { + #[error("Unknown filter byte code {code}")] + UnknownFilterByteCode { code: u8 }, + #[error("Unknown arg byte code {code}")] + UnknownArgByteCode { code: u8 }, + #[error("Unknown byte code {code}")] + UnknownByteCode { code: u8 }, + #[error("Unexpected EoF, expected {expected}")] + UnexpectedEof { expected: &'static str }, + // #[error("Byte array error: {0}")] + // ByteArrayError(#[from] ByteArrayError), + #[error("Tari script error: {0}")] + ScriptError(#[from] ScriptError), + #[error(transparent)] + Io(#[from] io::Error), +} + +pub(super) trait CovenentReadExt: io::Read { + fn read_next_byte_code(&mut self) -> Result, io::Error>; + fn read_variable_length_bytes(&mut self, size: usize) -> Result, io::Error>; +} + +impl CovenentReadExt for R { + fn read_next_byte_code(&mut self) -> Result, io::Error> { + let mut buf = [0u8; 1]; + loop { + // This is what read_exact does, except that if we read 0 bytes, we return None instead of an UnexpectedEof + // error + match self.read(&mut buf) { + Ok(0) => return Ok(None), + Ok(1) => return Ok(Some(buf[0])), + Ok(_) => unreachable!("buffer size is 1 but more bytes were read!?"), + Err(ref err) if err.kind() == io::ErrorKind::Interrupted => {}, + Err(err) => return Err(err), + } + } + } + + fn read_variable_length_bytes(&mut self, max_size: usize) -> Result, io::Error> { + let len = self.read_varint::()? as usize; + if len > max_size { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!( + "Received variable length bytes that exceed {} bytes (max: {})", + len, max_size + ), + )); + } + let mut buf = vec![0u8; len]; + self.read_exact(&mut buf)?; + Ok(buf) + } +} + +#[cfg(test)] +mod test { + use tari_test_utils::unpack_enum; + use tari_utilities::hex::{from_hex, to_hex}; + + use super::*; + use crate::{ + consensus::ToConsensusBytes, + covenant, + covenants::{arguments::CovenantArg, fields::OutputField, filters::CovenantFilter}, + }; + + #[test] + fn it_decodes_from_well_formed_bytes() { + let hash = from_hex("53563b674ba8e5166adb57afa8355bcf2ee759941eef8f8959b802367c2558bd").unwrap(); + let mut hash_buf = [0u8; 32]; + hash_buf.copy_from_slice(hash.as_slice()); + let bytes = covenant!(fields_hashed_eq( + @fields(@field::commitment, @field::features_metadata), + @hash(hash_buf), + )) + .to_consensus_bytes(); + let mut buf = bytes.as_slice(); + let mut decoder = CovenantTokenDecoder::new(&mut buf); + let token = decoder.next().unwrap().unwrap(); + assert!(matches!( + token, + CovenantToken::Filter(CovenantFilter::FieldsHashedEq(_)) + )); + let token = decoder.next().unwrap().unwrap(); + unpack_enum!(CovenantArg::OutputFields(fields) = token.as_arg().unwrap()); + assert_eq!(fields.fields(), &[ + OutputField::Commitment, + OutputField::FeaturesMetadata + ]); + + let token = decoder.next().unwrap().unwrap(); + unpack_enum!(CovenantArg::Hash(hash) = token.as_arg().unwrap()); + assert_eq!( + to_hex(&hash[..]), + "53563b674ba8e5166adb57afa8355bcf2ee759941eef8f8959b802367c2558bd" + ); + + assert!(decoder.next().is_none()); + } +} diff --git a/base_layer/core/src/covenants/encoder.rs b/base_layer/core/src/covenants/encoder.rs new file mode 100644 index 0000000000..b4269fe3b9 --- /dev/null +++ b/base_layer/core/src/covenants/encoder.rs @@ -0,0 +1,65 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{convert::TryFrom, io}; + +use integer_encoding::VarIntWriter; + +use crate::covenants::token::CovenantToken; + +pub struct CovenantTokenEncoder<'a> { + tokens: &'a [CovenantToken], +} + +impl<'a> CovenantTokenEncoder<'a> { + pub fn new(tokens: &'a [CovenantToken]) -> Self { + Self { tokens } + } + + pub fn write_to(&self, writer: &mut W) -> Result { + let mut written = 0; + for token in self.tokens { + written += token.write_to(writer)?; + } + Ok(written) + } +} + +pub(super) trait CovenentWriteExt: io::Write { + fn write_u8_fixed(&mut self, v: u8) -> Result; + fn write_variable_length_bytes(&mut self, buf: &[u8]) -> Result; +} + +impl CovenentWriteExt for W { + fn write_u8_fixed(&mut self, v: u8) -> Result { + self.write_all(&[v])?; + Ok(1) + } + + fn write_variable_length_bytes(&mut self, buf: &[u8]) -> Result { + let len = u16::try_from(buf.len()).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?; + let mut written = self.write_varint(len)?; + written += buf.len(); + self.write_all(buf)?; + Ok(written) + } +} diff --git a/base_layer/core/src/covenants/error.rs b/base_layer/core/src/covenants/error.rs new file mode 100644 index 0000000000..462a6de603 --- /dev/null +++ b/base_layer/core/src/covenants/error.rs @@ -0,0 +1,45 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::covenants::decoder::CovenantDecodeError; + +#[derive(Debug, thiserror::Error)] +pub enum CovenantError { + #[error("Reached the end of tokens but another token was expected")] + UnexpectedEndOfTokens, + #[error("Covenant decode error: {0}")] + CovenantDecodeError(#[from] CovenantDecodeError), + #[error("Expected an argument but got a filter")] + ExpectedArgButGotFilter, + #[error("Expected a filter but got an argument")] + ExpectedFilterButGotArg, + #[error("Encountered an unexpected argument. Expected {expected} but got {got}")] + UnexpectedArgument { expected: &'static str, got: String }, + #[error("Covenant failed: no matching outputs found")] + NoMatchingOutputs, + #[error("Covenant failed: unused tokens remain after execution")] + RemainingTokens, + #[error("Invalid argument for filter {filter}: {details}")] + InvalidArgument { filter: &'static str, details: String }, + #[error("Unsupported argument {arg}: {details}")] + UnsupportedArgument { arg: &'static str, details: String }, +} diff --git a/base_layer/core/src/covenants/fields.rs b/base_layer/core/src/covenants/fields.rs new file mode 100644 index 0000000000..fbe271e989 --- /dev/null +++ b/base_layer/core/src/covenants/fields.rs @@ -0,0 +1,353 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ + any::Any, + fmt::{Display, Formatter}, + io, + iter::FromIterator, +}; + +use digest::Digest; +use integer_encoding::VarIntWriter; +use tari_common_types::types::Challenge; + +use crate::{ + consensus::ToConsensusBytes, + covenants::{ + byte_codes, + decoder::{CovenantDecodeError, CovenentReadExt}, + encoder::CovenentWriteExt, + error::CovenantError, + }, + transactions::transaction::{TransactionInput, TransactionOutput}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum OutputField { + Commitment = byte_codes::FIELD_COMMITMENT, + Script = byte_codes::FIELD_SCRIPT, + SenderOffsetPublicKey = byte_codes::FIELD_SENDER_OFFSET_PUBLIC_KEY, + Covenant = byte_codes::FIELD_COVENANT, + Features = byte_codes::FIELD_FEATURES, + FeaturesFlags = byte_codes::FIELD_FEATURES_FLAGS, + FeaturesMaturity = byte_codes::FIELD_FEATURES_MATURITY, + FeaturesUniqueId = byte_codes::FIELD_FEATURES_UNIQUE_ID, + FeaturesParentPublicKey = byte_codes::FIELD_FEATURES_PARENT_PUBLIC_KEY, + FeaturesMetadata = byte_codes::FIELD_FEATURES_METADATA, +} + +impl OutputField { + pub fn from_byte(byte: u8) -> Result { + use byte_codes::*; + use OutputField::*; + match byte { + FIELD_COMMITMENT => Ok(Commitment), + FIELD_SCRIPT => Ok(Script), + FIELD_SENDER_OFFSET_PUBLIC_KEY => Ok(SenderOffsetPublicKey), + FIELD_COVENANT => Ok(Covenant), + FIELD_FEATURES => Ok(Features), + FIELD_FEATURES_FLAGS => Ok(FeaturesFlags), + FIELD_FEATURES_MATURITY => Ok(FeaturesMaturity), + FIELD_FEATURES_UNIQUE_ID => Ok(FeaturesUniqueId), + FIELD_FEATURES_PARENT_PUBLIC_KEY => Ok(FeaturesParentPublicKey), + FIELD_FEATURES_METADATA => Ok(FeaturesMetadata), + + _ => Err(CovenantDecodeError::UnknownByteCode { code: byte }), + } + } + + pub fn as_byte(&self) -> u8 { + *self as u8 + } + + pub fn get_field_value_ref<'a, T: 'static>(&self, output: &'a TransactionOutput) -> Option<&'a T> { + use OutputField::*; + let val = match self { + Commitment => &output.commitment as &dyn Any, + Script => &output.script as &dyn Any, + SenderOffsetPublicKey => &output.sender_offset_public_key as &dyn Any, + Covenant => unimplemented!(), + Features => &output.features as &dyn Any, + FeaturesFlags => &output.features.flags as &dyn Any, + FeaturesMaturity => &output.features.maturity as &dyn Any, + FeaturesUniqueId => &output.features.unique_id as &dyn Any, + FeaturesParentPublicKey => &output.features.parent_public_key as &dyn Any, + FeaturesMetadata => &output.features.metadata as &dyn Any, + }; + val.downcast_ref::() + } + + pub fn get_field_value_bytes(&self, output: &TransactionOutput) -> Vec { + use OutputField::*; + match self { + Commitment => output.commitment.to_consensus_bytes(), + Script => output.script.to_consensus_bytes(), + SenderOffsetPublicKey => output.sender_offset_public_key.to_consensus_bytes(), + Covenant => unimplemented!(), + Features => output.features.to_consensus_bytes(), + FeaturesFlags => output.features.flags.to_consensus_bytes(), + FeaturesMaturity => output.features.maturity.to_consensus_bytes(), + FeaturesUniqueId => output + .features + .unique_id + .as_ref() + .map(|unique_id| unique_id.to_consensus_bytes()) + .unwrap_or_default(), + FeaturesParentPublicKey => output + .features + .parent_public_key + .as_ref() + .map(|pk| pk.to_consensus_bytes()) + .unwrap_or_default(), + FeaturesMetadata => output.features.metadata.to_consensus_bytes(), + } + } + + pub fn is_eq_input(&self, input: &TransactionInput, output: &TransactionOutput) -> bool { + use OutputField::*; + match self { + Commitment => input + .commitment() + .map(|commitment| *commitment == output.commitment) + .unwrap_or(false), + Script => input.script().map(|script| *script == output.script).unwrap_or(false), + SenderOffsetPublicKey => input + .sender_offset_public_key() + .map(|sender_offset_public_key| *sender_offset_public_key == output.sender_offset_public_key) + .unwrap_or(false), + Covenant => unimplemented!(), + Features => input + .features() + .map(|features| *features == output.features) + .unwrap_or(false), + FeaturesFlags => input + .features() + .map(|features| features.flags == output.features.flags) + .unwrap_or(false), + FeaturesMaturity => input + .features() + .map(|features| features.maturity == output.features.maturity) + .unwrap_or(false), + FeaturesUniqueId => input + .features() + .map(|features| features.unique_id == output.features.unique_id) + .unwrap_or(false), + FeaturesParentPublicKey => input + .features() + .map(|features| features.parent_public_key == output.features.parent_public_key) + .unwrap_or(false), + FeaturesMetadata => input + .features() + .map(|features| features.metadata == output.features.metadata) + .unwrap_or(false), + } + } + + pub fn is_eq(&self, output: &TransactionOutput, val: &T) -> Result { + use OutputField::*; + match self { + // Handle edge cases + FeaturesParentPublicKey | FeaturesUniqueId => match self.get_field_value_ref::>(output) { + Some(Some(field_val)) => Ok(field_val == val), + _ => Ok(false), + }, + Features => Err(CovenantError::UnsupportedArgument { + arg: "features", + details: "OutputFeatures is not supported for operation is_eq".to_string(), + }), + _ => match self.get_field_value_ref::(output) { + Some(field_val) => Ok(field_val == val), + None => Err(CovenantError::InvalidArgument { + filter: "is_eq", + details: format!("Invalid type for field {}", self), + }), + }, + } + } + + //---------------------------------- Macro helpers --------------------------------------------// + #[allow(dead_code)] + pub(super) fn commitment() -> Self { + OutputField::Commitment + } + + #[allow(dead_code)] + pub(super) fn script() -> Self { + OutputField::Script + } + + #[allow(dead_code)] + pub(super) fn sender_offset_public_key() -> Self { + OutputField::SenderOffsetPublicKey + } + + #[allow(dead_code)] + pub(super) fn covenant() -> Self { + OutputField::Covenant + } + + #[allow(dead_code)] + pub(super) fn features() -> Self { + OutputField::Features + } + + #[allow(dead_code)] + pub(super) fn features_flags() -> Self { + OutputField::FeaturesFlags + } + + #[allow(dead_code)] + pub(super) fn features_maturity() -> Self { + OutputField::FeaturesMaturity + } + + #[allow(dead_code)] + pub(super) fn features_unique_id() -> Self { + OutputField::FeaturesUniqueId + } + + #[allow(dead_code)] + pub(super) fn features_parent_public_key() -> Self { + OutputField::FeaturesParentPublicKey + } + + #[allow(dead_code)] + pub(super) fn features_metadata() -> Self { + OutputField::FeaturesMetadata + } +} + +impl Display for OutputField { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use OutputField::*; + match self { + Commitment => write!(f, "field::commitment"), + SenderOffsetPublicKey => write!(f, "field::sender_offset_public_key"), + Script => write!(f, "field::script"), + Covenant => write!(f, "field::covenant"), + Features => write!(f, "field::features"), + FeaturesFlags => write!(f, "field::features_flags"), + FeaturesUniqueId => write!(f, "field::features_unique_id"), + FeaturesMetadata => write!(f, "field::features_metadata"), + FeaturesParentPublicKey => write!(f, "field::features_parent_public_key"), + FeaturesMaturity => write!(f, "field::features_maturity"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct OutputFields { + fields: Vec, +} + +impl OutputFields { + /// The number of unique fields available. This always matches the number of variants in `OutputField`. + pub const NUM_FIELDS: usize = 10; + + pub fn new() -> Self { + Default::default() + } + + pub fn push(&mut self, field: OutputField) { + self.fields.push(field); + } + + pub fn read_from(reader: &mut R) -> Result { + // Each field is a byte + let buf = reader.read_variable_length_bytes(Self::NUM_FIELDS)?; + buf.iter().map(|byte| OutputField::from_byte(*byte)).collect() + } + + pub fn write_to(&self, writer: &mut W) -> Result { + let len = self.fields.len(); + if len > Self::NUM_FIELDS { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "tried to write more than maximum number of fields", + )); + } + let mut written = writer.write_varint(len)?; + for byte in self.iter().map(|f| f.as_byte()) { + written += writer.write_u8_fixed(byte)?; + } + Ok(written) + } + + pub fn iter(&self) -> impl Iterator + '_ { + self.fields.iter() + } + + pub fn len(&self) -> usize { + self.fields.len() + } + + pub fn is_empty(&self) -> bool { + self.fields.is_empty() + } + + pub fn construct_challenge_from(&self, output: &TransactionOutput) -> Challenge { + let mut challenge = Challenge::new(); + for field in self.fields.iter() { + challenge = challenge.chain(field.get_field_value_bytes(output)); + } + challenge + } + + pub fn fields(&self) -> &[OutputField] { + &self.fields + } +} + +impl From> for OutputFields { + fn from(fields: Vec) -> Self { + OutputFields { fields } + } +} +impl FromIterator for OutputFields { + fn from_iter>(iter: T) -> Self { + Self { + fields: iter.into_iter().collect(), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::transactions::transaction::OutputFeatures; + + #[test] + fn get_field_value_ref() { + let output = TransactionOutput::new( + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ); + let r = OutputField::Features.get_field_value_ref::(&output); + assert_eq!(*r.unwrap(), OutputFeatures::default()); + } +} diff --git a/base_layer/core/src/covenants/filters/absolute_height.rs b/base_layer/core/src/covenants/filters/absolute_height.rs new file mode 100644 index 0000000000..63d8cb9e05 --- /dev/null +++ b/base_layer/core/src/covenants/filters/absolute_height.rs @@ -0,0 +1,104 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AbsoluteHeightFilter; + +impl Filter for AbsoluteHeightFilter { + fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { + let abs_height = context.next_arg()?.require_uint()?; + let current_height = context.block_height(); + if current_height < abs_height { + output_set.clear(); + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + covenant, + covenants::{filters::test::setup_filter_test, test::create_input}, + }; + + #[test] + fn it_filters_all_out_if_height_not_reached() { + let covenant = covenant!(absolute_height(@uint(100))); + let input = create_input(); + let (mut context, outputs) = setup_filter_test(&covenant, &input, 42, |_| {}); + + let mut output_set = OutputSet::new(&outputs); + AbsoluteHeightFilter.filter(&mut context, &mut output_set).unwrap(); + + assert!(output_set.is_empty()); + } + + #[test] + fn it_filters_all_in_if_height_reached() { + let covenant = covenant!(absolute_height(@uint(100))); + let input = create_input(); + let (mut context, outputs) = setup_filter_test(&covenant, &input, 100, |_| {}); + + let mut output_set = OutputSet::new(&outputs); + AbsoluteHeightFilter.filter(&mut context, &mut output_set).unwrap(); + + assert_eq!(output_set.len(), 10); + } + + #[test] + fn it_filters_all_in_if_height_exceeded() { + let covenant = covenant!(absolute_height(@uint(42))); + let input = create_input(); + let (mut context, outputs) = setup_filter_test(&covenant, &input, 100, |_| {}); + + let mut output_set = OutputSet::new(&outputs); + AbsoluteHeightFilter.filter(&mut context, &mut output_set).unwrap(); + + assert_eq!(output_set.len(), 10); + } +} diff --git a/base_layer/core/src/covenants/filters/and.rs b/base_layer/core/src/covenants/filters/and.rs new file mode 100644 index 0000000000..4c32a704f1 --- /dev/null +++ b/base_layer/core/src/covenants/filters/and.rs @@ -0,0 +1,69 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AndFilter; + +impl Filter for AndFilter { + fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { + let a = context.require_next_filter()?; + a.filter(context, output_set)?; + let b = context.require_next_filter()?; + b.filter(context, output_set)?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + + use super::*; + use crate::{ + covenant, + covenants::{filters::test::setup_filter_test, test::create_input}, + }; + + #[test] + fn it_filters_outputset_using_intersection() { + let bytes = vec![0xab, 0xcd, 0xef]; + let covenant = covenant!(and(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::features_unique_id, @bytes(bytes.clone())))); + let input = create_input(); + let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |outputs| { + outputs[5].features.maturity = 42; + outputs[5].features.unique_id = Some(bytes.clone()); + outputs[7].features.maturity = 42; + outputs[7].features.unique_id = Some(bytes.clone()); + // Does not have maturity = 42 + outputs[8].features.maturity = 123; + outputs[8].features.unique_id = Some(bytes.clone()); + }); + + let mut output_set = OutputSet::new(&outputs); + AndFilter.filter(&mut context, &mut output_set).unwrap(); + + assert_eq!(output_set.len(), 2); + assert_eq!(output_set.get_selected_indexes(), vec![5, 7]); + } +} diff --git a/base_layer/core/src/covenants/filters/field_eq.rs b/base_layer/core/src/covenants/filters/field_eq.rs new file mode 100644 index 0000000000..2b7670646b --- /dev/null +++ b/base_layer/core/src/covenants/filters/field_eq.rs @@ -0,0 +1,218 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::covenants::{ + arguments::CovenantArg, + context::CovenantContext, + error::CovenantError, + filters::Filter, + output_set::OutputSet, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FieldEqFilter; + +impl Filter for FieldEqFilter { + fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { + let field = context.next_arg()?.require_outputfield()?; + let arg = context.next_arg()?; + output_set.retain(|output| { + use CovenantArg::*; + match &arg { + Hash(hash) => field.is_eq(output, hash), + PublicKey(pk) => field.is_eq(output, pk), + Commitment(commitment) => field.is_eq(output, commitment), + TariScript(script) => field.is_eq(output, script), + Covenant(covenant) => field.is_eq(output, covenant), + Uint(int) => { + let val = field + .get_field_value_ref::(output) + .copied() + .or_else(|| field.get_field_value_ref::(output).map(|v| *v as u64)); + + match val { + Some(val) => Ok(val == *int), + None => Err(CovenantError::InvalidArgument { + filter: "fields_eq", + details: "Uint argument cannot be compared to non-numeric field".to_string(), + }), + } + }, + Bytes(bytes) => field.is_eq(output, bytes), + OutputField(_) | OutputFields(_) => Err(CovenantError::InvalidArgument { + filter: "field_eq", + details: "Invalid argument: fields are not a valid argument for field_eq".to_string(), + }), + } + })?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use tari_common_types::types::{Commitment, PublicKey}; + use tari_crypto::script; + use tari_test_utils::unpack_enum; + use tari_utilities::hex::Hex; + + use super::*; + use crate::{ + covenant, + covenants::test::{create_context, create_input, create_outputs}, + }; + + #[test] + fn it_filters_uint() { + let covenant = covenant!(field_eq(@field::features_maturity, @uint(42))); + let input = create_input(); + let mut context = create_context(&covenant, &input, 0); + // Remove `field_eq` + context.next_filter().unwrap(); + let mut outputs = create_outputs(10, Default::default()); + outputs[5].features.maturity = 42; + let mut output_set = OutputSet::new(&outputs); + FieldEqFilter.filter(&mut context, &mut output_set).unwrap(); + + assert_eq!(output_set.len(), 1); + assert_eq!(output_set.get(5).unwrap().features.maturity, 42); + } + + #[test] + fn it_filters_sender_offset_public_key() { + let pk = PublicKey::from_hex("5615a327e1d19da34e5aa8bbd2ecc97addf29b158844b885bfc4efa0dab17052").unwrap(); + let covenant = covenant!(field_eq( + @field::features_parent_public_key, + @public_key(pk.clone()) + )); + let input = create_input(); + let mut context = create_context(&covenant, &input, 0); + // Remove `field_eq` + context.next_filter().unwrap(); + let mut outputs = create_outputs(10, Default::default()); + outputs[5].features.parent_public_key = Some(pk.clone()); + let mut output_set = OutputSet::new(&outputs); + FieldEqFilter.filter(&mut context, &mut output_set).unwrap(); + + assert_eq!(output_set.len(), 1); + assert_eq!( + *output_set.get(5).unwrap().features.parent_public_key.as_ref().unwrap(), + pk + ); + } + + #[test] + fn it_filters_commitment() { + let commitment = + Commitment::from_hex("7ca31ba517d8b563609ed6707fedde5a2be64ac1d67b254cb5348bc2f680557f").unwrap(); + let covenant = covenant!(field_eq( + @field::commitment, + @commitment(commitment.clone()) + )); + let input = create_input(); + let mut context = create_context(&covenant, &input, 0); + // Remove `field_eq` + context.next_filter().unwrap(); + let mut outputs = create_outputs(10, Default::default()); + outputs[5].commitment = commitment.clone(); + outputs[7].commitment = commitment; + let mut output_set = OutputSet::new(&outputs); + FieldEqFilter.filter(&mut context, &mut output_set).unwrap(); + + assert_eq!(output_set.len(), 2); + assert_eq!(output_set.get_selected_indexes(), vec![5, 7]); + } + + #[test] + fn it_filters_tari_script() { + let script = script!(CheckHeight(100)); + let covenant = covenant!(field_eq( + @field::script, + @script(script.clone()) + )); + let input = create_input(); + let mut context = create_context(&covenant, &input, 0); + // Remove `field_eq` + context.next_filter().unwrap(); + let mut outputs = create_outputs(10, Default::default()); + outputs[5].script = script.clone(); + outputs[7].script = script; + let mut output_set = OutputSet::new(&outputs); + FieldEqFilter.filter(&mut context, &mut output_set).unwrap(); + + assert_eq!(output_set.len(), 2); + assert_eq!(output_set.get_selected_indexes(), vec![5, 7]); + } + + // #[test] + // fn it_filters_covenant() { + // // TODO: Covenant field is not in output yet + // let covenant = covenant!(identity()); + // let covenant = covenant!(field_eq( + // @field::covenant, + // @covenant(covenant.clone()) + // )); + // let input = create_input(); + // let mut context = create_context(&covenant, &input, 0); + // // Remove `field_eq` + // context.next_filter().unwrap(); + // let mut outputs = create_outputs(10, Default::default()); + // outputs[5].covenant = covenant.clone(); + // outputs[7].covenant = covenant.clone(); + // let mut output_set = OutputSet::new(&outputs); + // FieldEqFilter.filter(&mut context, &mut output_set).unwrap(); + // + // assert_eq!(output_set.len(), 2); + // assert_eq!(output_set.get_selected_indexes(), vec![5, 7]); + // } + + #[test] + fn it_errors_for_unsupported_features_field() { + let covenant = covenant!(field_eq( + @field::features, + @bytes(vec![]) + )); + let input = create_input(); + let mut context = create_context(&covenant, &input, 0); + // Remove `field_eq` + context.next_filter().unwrap(); + let outputs = create_outputs(10, Default::default()); + let mut output_set = OutputSet::new(&outputs); + let err = FieldEqFilter.filter(&mut context, &mut output_set).unwrap_err(); + unpack_enum!(CovenantError::UnsupportedArgument { arg, .. } = err); + assert_eq!(arg, "features"); + } + + #[test] + fn it_errors_if_field_has_an_incorrect_type() { + let covenant = covenant!(field_eq(@field::features, @uint(42))); + let input = create_input(); + let mut context = create_context(&covenant, &input, 0); + // Remove `field_eq` + context.next_filter().unwrap(); + let outputs = create_outputs(10, Default::default()); + let mut output_set = OutputSet::new(&outputs); + let err = FieldEqFilter.filter(&mut context, &mut output_set).unwrap_err(); + unpack_enum!(CovenantError::InvalidArgument { .. } = err); + } +} diff --git a/base_layer/core/src/covenants/filters/fields_hashed_eq.rs b/base_layer/core/src/covenants/filters/fields_hashed_eq.rs new file mode 100644 index 0000000000..b6a3f1f6bf --- /dev/null +++ b/base_layer/core/src/covenants/filters/fields_hashed_eq.rs @@ -0,0 +1,97 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use digest::Digest; + +use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FieldsHashedEqFilter; + +impl Filter for FieldsHashedEqFilter { + fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { + let fields = context.next_arg()?.require_outputfields()?; + let hash = context.next_arg()?.require_hash()?; + output_set.retain(|output| { + let challenge = fields.construct_challenge_from(output); + Ok(challenge.finalize()[..] == hash) + })?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use tari_common_types::types::Challenge; + + use super::*; + use crate::{ + covenant, + covenants::{filters::test::setup_filter_test, test::create_input}, + transactions::transaction::OutputFeatures, + }; + + #[test] + fn it_filters_outputs_with_fields_that_hash_to_given_hash() { + let features = OutputFeatures { + maturity: 42, + unique_id: Some(vec![0xab, 0xcd, 0xef]), + ..Default::default() + }; + let hashed = Challenge::new().chain(features.to_consensus_bytes()).finalize(); + let mut hash = [0u8; 32]; + hash.copy_from_slice(hashed.as_slice()); + let covenant = covenant!(fields_hashed_eq(@fields(@field::features), @hash(hash))); + let input = create_input(); + let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |outputs| { + outputs[5].features = features.clone(); + outputs[7].features = features; + }); + let mut output_set = OutputSet::new(&outputs); + FieldsHashedEqFilter.filter(&mut context, &mut output_set).unwrap(); + + assert_eq!(output_set.len(), 2); + assert_eq!(output_set.get_selected_indexes(), vec![5, 7]); + } +} diff --git a/base_layer/core/src/covenants/filters/fields_preserved.rs b/base_layer/core/src/covenants/filters/fields_preserved.rs new file mode 100644 index 0000000000..8f38810273 --- /dev/null +++ b/base_layer/core/src/covenants/filters/fields_preserved.rs @@ -0,0 +1,73 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FieldsPreservedFilter; + +impl Filter for FieldsPreservedFilter { + fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { + let fields = context.next_arg()?.require_outputfields()?; + let input = context.input(); + output_set.retain(|output| Ok(fields.iter().all(|field| field.is_eq_input(input, output))))?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + + use super::*; + use crate::{ + covenant, + covenants::{filters::test::setup_filter_test, test::create_input}, + transactions::transaction::OutputFlags, + }; + + #[test] + fn it_filters_outputs_that_match_input_fields() { + let bytes = vec![0xab, 0xcd, 0xef]; + let covenant = covenant!(fields_preserved(@fields(@field::features_maturity, @field::features_unique_id, @field::features_flags))); + let mut input = create_input(); + input.set_maturity(42).unwrap(); + input.features_mut().unwrap().unique_id = Some(bytes.clone()); + input.features_mut().unwrap().flags = OutputFlags::SIDECHAIN_CHECKPOINT; + let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |outputs| { + outputs[5].features.maturity = 42; + outputs[5].features.unique_id = Some(bytes.clone()); + outputs[5].features.flags = OutputFlags::SIDECHAIN_CHECKPOINT; + outputs[7].features.maturity = 42; + outputs[7].features.flags = OutputFlags::SIDECHAIN_CHECKPOINT; + outputs[7].features.unique_id = Some(vec![0x01, 0x02]); + outputs[8].features.maturity = 42; + outputs[8].features.unique_id = Some(bytes.clone()); + outputs[8].features.flags = OutputFlags::SIDECHAIN_CHECKPOINT | OutputFlags::COINBASE_OUTPUT; + }); + let mut output_set = OutputSet::new(&outputs); + + FieldsPreservedFilter.filter(&mut context, &mut output_set).unwrap(); + + assert_eq!(output_set.len(), 1); + assert_eq!(output_set.get_selected_indexes(), vec![5]); + } +} diff --git a/base_layer/core/src/covenants/filters/filter.rs b/base_layer/core/src/covenants/filters/filter.rs new file mode 100644 index 0000000000..fe91c3d15e --- /dev/null +++ b/base_layer/core/src/covenants/filters/filter.rs @@ -0,0 +1,165 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::io; + +use super::{ + absolute_height::AbsoluteHeightFilter, + and::AndFilter, + field_eq::FieldEqFilter, + fields_hashed_eq::FieldsHashedEqFilter, + fields_preserved::FieldsPreservedFilter, + identity::IdentityFilter, + not::NotFilter, + or::OrFilter, + output_hash_eq::OutputHashEqFilter, + xor::XorFilter, +}; +use crate::covenants::{ + byte_codes, + context::CovenantContext, + decoder::CovenantDecodeError, + encoder::CovenentWriteExt, + error::CovenantError, + output_set::OutputSet, +}; + +pub trait Filter { + fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError>; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CovenantFilter { + Identity(IdentityFilter), + And(AndFilter), + Or(OrFilter), + Xor(XorFilter), + Not(NotFilter), + OutputHashEq(OutputHashEqFilter), + FieldsPreserved(FieldsPreservedFilter), + FieldEq(FieldEqFilter), + FieldsHashedEq(FieldsHashedEqFilter), + AbsoluteHeight(AbsoluteHeightFilter), +} + +impl CovenantFilter { + pub fn is_valid_code(code: u8) -> bool { + byte_codes::is_valid_filter_code(code) + } + + pub fn write_to(&self, writer: &mut W) -> Result { + writer.write_u8_fixed(self.as_byte_code()) + } + + fn as_byte_code(&self) -> u8 { + use byte_codes::*; + use CovenantFilter::*; + + match self { + Identity(_) => FILTER_IDENTITY, + And(_) => FILTER_AND, + Or(_) => FILTER_OR, + Xor(_) => FILTER_XOR, + Not(_) => FILTER_NOT, + OutputHashEq(_) => FILTER_OUTPUT_HASH_EQ, + FieldsPreserved(_) => FILTER_FIELDS_PRESERVED, + FieldEq(_) => FILTER_FIELD_EQ, + FieldsHashedEq(_) => FILTER_FIELDS_HASHED_EQ, + AbsoluteHeight(_) => FILTER_ABSOLUTE_HEIGHT, + } + } + + pub fn try_from_byte_code(code: u8) -> Result { + use byte_codes::*; + match code { + FILTER_IDENTITY => Ok(Self::identity()), + FILTER_AND => Ok(Self::and()), + FILTER_OR => Ok(Self::or()), + FILTER_XOR => Ok(Self::xor()), + FILTER_NOT => Ok(Self::not()), + FILTER_OUTPUT_HASH_EQ => Ok(Self::output_hash_eq()), + FILTER_FIELDS_PRESERVED => Ok(Self::fields_preserved()), + FILTER_FIELD_EQ => Ok(Self::field_eq()), + FILTER_FIELDS_HASHED_EQ => Ok(Self::fields_hashed_eq()), + FILTER_ABSOLUTE_HEIGHT => Ok(Self::absolute_height()), + _ => Err(CovenantDecodeError::UnknownFilterByteCode { code }), + } + } + + pub fn identity() -> Self { + CovenantFilter::Identity(IdentityFilter) + } + + pub fn and() -> Self { + CovenantFilter::And(AndFilter) + } + + pub fn or() -> Self { + CovenantFilter::Or(OrFilter) + } + + pub fn xor() -> Self { + CovenantFilter::Xor(XorFilter) + } + + pub fn not() -> Self { + CovenantFilter::Not(NotFilter) + } + + pub fn output_hash_eq() -> Self { + CovenantFilter::OutputHashEq(OutputHashEqFilter) + } + + pub fn fields_preserved() -> Self { + CovenantFilter::FieldsPreserved(FieldsPreservedFilter) + } + + pub fn field_eq() -> Self { + CovenantFilter::FieldEq(FieldEqFilter) + } + + pub fn fields_hashed_eq() -> Self { + CovenantFilter::FieldsHashedEq(FieldsHashedEqFilter) + } + + pub fn absolute_height() -> Self { + CovenantFilter::AbsoluteHeight(AbsoluteHeightFilter) + } +} + +impl Filter for CovenantFilter { + fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { + use CovenantFilter::*; + match self { + Identity(identity) => identity.filter(context, output_set), + And(and) => and.filter(context, output_set), + Or(or) => or.filter(context, output_set), + Xor(xor) => xor.filter(context, output_set), + Not(not) => not.filter(context, output_set), + OutputHashEq(output_hash_eq) => output_hash_eq.filter(context, output_set), + FieldsPreserved(fields_preserved) => fields_preserved.filter(context, output_set), + FieldEq(fields_eq) => fields_eq.filter(context, output_set), + FieldsHashedEq(fields_hashed_eq) => fields_hashed_eq.filter(context, output_set), + AbsoluteHeight(abs_height) => abs_height.filter(context, output_set), + } + } +} diff --git a/base_layer/core/src/covenants/filters/identity.rs b/base_layer/core/src/covenants/filters/identity.rs new file mode 100644 index 0000000000..1748c97810 --- /dev/null +++ b/base_layer/core/src/covenants/filters/identity.rs @@ -0,0 +1,32 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IdentityFilter; + +impl Filter for IdentityFilter { + fn filter(&self, _: &mut CovenantContext<'_>, _: &mut OutputSet<'_>) -> Result<(), CovenantError> { + Ok(()) + } +} diff --git a/base_layer/core/src/covenants/filters/mod.rs b/base_layer/core/src/covenants/filters/mod.rs new file mode 100644 index 0000000000..59cb3979d2 --- /dev/null +++ b/base_layer/core/src/covenants/filters/mod.rs @@ -0,0 +1,49 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod absolute_height; +mod and; +mod field_eq; +mod fields_hashed_eq; +mod fields_preserved; +mod identity; +mod not; +mod or; +mod output_hash_eq; +mod xor; + +pub use absolute_height::AbsoluteHeightFilter; +pub use and::AndFilter; +pub use field_eq::FieldEqFilter; +pub use fields_hashed_eq::FieldsHashedEqFilter; +pub use fields_preserved::FieldsPreservedFilter; +pub use identity::IdentityFilter; +pub use not::NotFilter; +pub use or::OrFilter; +pub use output_hash_eq::OutputHashEqFilter; +pub use xor::XorFilter; + +mod filter; +pub use filter::{CovenantFilter, Filter}; + +#[cfg(test)] +mod test; diff --git a/base_layer/core/src/covenants/filters/not.rs b/base_layer/core/src/covenants/filters/not.rs new file mode 100644 index 0000000000..5c02587728 --- /dev/null +++ b/base_layer/core/src/covenants/filters/not.rs @@ -0,0 +1,64 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NotFilter; + +impl Filter for NotFilter { + fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { + let filter = context.require_next_filter()?; + let mut output_set_copy = output_set.clone(); + filter.filter(context, &mut output_set_copy)?; + output_set.set(output_set.difference(&output_set_copy)); + Ok(()) + } +} + +#[cfg(test)] +mod test { + + use super::*; + use crate::{ + covenant, + covenants::{filters::test::setup_filter_test, test::create_input}, + }; + + #[test] + fn it_filters_compliment_of_filter() { + let bytes = vec![0xab, 0xcd, 0xef]; + let covenant = covenant!(not(or(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::features_unique_id, @bytes(bytes.clone()))))); + let input = create_input(); + let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |outputs| { + outputs[5].features.maturity = 42; + outputs[5].features.unique_id = Some(bytes.clone()); + outputs[7].features.maturity = 42; + outputs[8].features.unique_id = Some(bytes.clone()); + }); + let mut output_set = OutputSet::new(&outputs); + NotFilter.filter(&mut context, &mut output_set).unwrap(); + + assert_eq!(output_set.len(), 7); + assert_eq!(output_set.get_selected_indexes(), vec![0, 1, 2, 3, 4, 6, 9]); + } +} diff --git a/base_layer/core/src/covenants/filters/or.rs b/base_layer/core/src/covenants/filters/or.rs new file mode 100644 index 0000000000..53a8ba053b --- /dev/null +++ b/base_layer/core/src/covenants/filters/or.rs @@ -0,0 +1,69 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OrFilter; + +impl Filter for OrFilter { + fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { + let a = context.require_next_filter()?; + let mut output_set_a = output_set.clone(); + a.filter(context, &mut output_set_a)?; + + let b = context.require_next_filter()?; + let mut output_set_b = output_set.clone(); + b.filter(context, &mut output_set_b)?; + + output_set.set(output_set_a.union(&output_set_b)); + Ok(()) + } +} + +#[cfg(test)] +mod test { + + use super::*; + use crate::{ + covenant, + covenants::{filters::test::setup_filter_test, test::create_input}, + }; + + #[test] + fn it_filters_outputset_using_union() { + let bytes = vec![0xab, 0xcd, 0xef]; + let covenant = covenant!(or(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::features_unique_id, @bytes(bytes.clone())))); + let input = create_input(); + let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |outputs| { + outputs[5].features.maturity = 42; + outputs[5].features.unique_id = Some(bytes.clone()); + outputs[7].features.maturity = 42; + outputs[8].features.unique_id = Some(bytes.clone()); + }); + let mut output_set = OutputSet::new(&outputs); + OrFilter.filter(&mut context, &mut output_set).unwrap(); + + assert_eq!(output_set.len(), 3); + assert_eq!(output_set.get_selected_indexes(), vec![5, 7, 8]); + } +} diff --git a/base_layer/core/src/covenants/filters/output_hash_eq.rs b/base_layer/core/src/covenants/filters/output_hash_eq.rs new file mode 100644 index 0000000000..a1a292dc0a --- /dev/null +++ b/base_layer/core/src/covenants/filters/output_hash_eq.rs @@ -0,0 +1,68 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use tari_utilities::Hashable; + +use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OutputHashEqFilter; + +impl Filter for OutputHashEqFilter { + fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { + let hash = context.next_arg()?.require_hash()?; + // An output's hash is unique so the output set is either 1 or 0 outputs will match + output_set.find_inplace(|output| output.hash() == hash); + Ok(()) + } +} + +#[cfg(test)] +mod test { + + use super::*; + use crate::{ + covenant, + covenants::{ + filters::test::setup_filter_test, + test::{create_input, create_outputs}, + }, + }; + + #[test] + fn it_filters_output_with_specific_hash() { + let output = create_outputs(1, Default::default()).remove(0); + let output_hash = output.hash(); + let mut hash = [0u8; 32]; + hash.copy_from_slice(output_hash.as_slice()); + let covenant = covenant!(output_hash_eq(@hash(hash))); + let input = create_input(); + let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, move |outputs| { + outputs.insert(5, output); + }); + let mut output_set = OutputSet::new(&outputs); + OutputHashEqFilter.filter(&mut context, &mut output_set).unwrap(); + + assert_eq!(output_set.len(), 1); + assert_eq!(output_set.get_selected_indexes(), vec![5]); + } +} diff --git a/base_layer/core/src/covenants/filters/test.rs b/base_layer/core/src/covenants/filters/test.rs new file mode 100644 index 0000000000..5169a14abb --- /dev/null +++ b/base_layer/core/src/covenants/filters/test.rs @@ -0,0 +1,47 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{ + covenants::{ + context::CovenantContext, + test::{create_context, create_outputs}, + Covenant, + }, + transactions::transaction::{TransactionInput, TransactionOutput}, +}; + +pub fn setup_filter_test<'a, F>( + covenant: &Covenant, + input: &'a TransactionInput, + block_height: u64, + output_mod: F, +) -> (CovenantContext<'a>, Vec) +where + F: FnOnce(&mut Vec), +{ + let mut context = create_context(covenant, input, block_height); + // Consume root token (i.e the filter we're testing), args for filter presumably come next + context.next_filter().unwrap(); + let mut outputs = create_outputs(10, Default::default()); + output_mod(&mut outputs); + (context, outputs) +} diff --git a/base_layer/core/src/covenants/filters/xor.rs b/base_layer/core/src/covenants/filters/xor.rs new file mode 100644 index 0000000000..b93f946a3f --- /dev/null +++ b/base_layer/core/src/covenants/filters/xor.rs @@ -0,0 +1,69 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct XorFilter; + +impl Filter for XorFilter { + fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { + let a = context.require_next_filter()?; + let mut output_set_a = output_set.clone(); + a.filter(context, &mut output_set_a)?; + + let b = context.require_next_filter()?; + let mut output_set_b = output_set.clone(); + b.filter(context, &mut output_set_b)?; + + output_set.set(output_set_a.symmetric_difference(output_set_b)); + Ok(()) + } +} + +#[cfg(test)] +mod test { + + use super::*; + use crate::{ + covenant, + covenants::{filters::test::setup_filter_test, test::create_input}, + }; + + #[test] + fn it_filters_outputset_using_symmetric_difference() { + let bytes = vec![0xab, 0xcd, 0xef]; + let covenant = covenant!(or(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::features_unique_id, @bytes(bytes.clone())))); + let input = create_input(); + let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |outputs| { + outputs[5].features.maturity = 42; + outputs[5].features.unique_id = Some(bytes.clone()); + outputs[7].features.maturity = 42; + outputs[8].features.unique_id = Some(bytes.clone()); + }); + let mut output_set = OutputSet::new(&outputs); + XorFilter.filter(&mut context, &mut output_set).unwrap(); + + assert_eq!(output_set.len(), 2); + assert_eq!(output_set.get_selected_indexes(), vec![7, 8]); + } +} diff --git a/base_layer/core/src/covenants/macros.rs b/base_layer/core/src/covenants/macros.rs new file mode 100644 index 0000000000..309ca37d40 --- /dev/null +++ b/base_layer/core/src/covenants/macros.rs @@ -0,0 +1,228 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// Simple syntax for expressing covenants. +/// +/// ```rust,ignore +/// // Before height 42, this may only be spent into an output with flag 8 (NON_FUNGIBLE) +/// let covenant = covenant!(or(absolute_height(@uint(42)), field_eq(@field::features_flags, @uint(8))); +/// covenant.execute(...)?; +/// ``` +#[macro_export] +macro_rules! covenant { + ($token:ident($($args:tt)*)) => {{ + let mut covenant = $crate::covenants::Covenant::new(); + $crate::__covenant_inner!(@ { covenant } $token($($args)*)); + covenant + }}; + + ($token:ident()) => {{ + let mut covenant = $crate::covenants::Covenant::new(); + $crate::__covenant_inner!(@ { covenant } $token()); + covenant + }}; + + () => { $crate::covenants::Covenant::new() }; +} + +#[macro_export] +macro_rules! __covenant_inner { + (@ { $covenant:ident }) => {}; + + // token() + (@ { $covenant:ident } $token:ident() $(,)?) => { + $covenant.push_token($crate::covenants::CovenantToken::$token()); + }; + + // @field::name, ... + (@ { $covenant:ident } @field::$field:ident, $($tail:tt)*) => { + $covenant.push_token($crate::covenants::CovenantToken::field($crate::covenants::OutputField::$field())); + $crate::__covenant_inner!(@ { $covenant } $($tail)*) + }; + + // @field::name + (@ { $covenant:ident } @field::$field:ident $(,)?) => { + $crate::__covenant_inner!(@ { $covenant } @field::$field,) + }; + + // @fields(@field::name, ...) + (@ { $covenant:ident } @fields($(@field::$field:ident),+ $(,)?)) => { + $crate::__covenant_inner!(@ { $covenant } @fields($(@field::$field),+),) + }; + + // @fields(@field::name, ...), ... + (@ { $covenant:ident } @fields($(@field::$field:ident),+ $(,)?), $($tail:tt)*) => { + $covenant.push_token($crate::covenants::CovenantToken::fields(vec![ + $($crate::covenants::OutputField::$field()),+ + ])); + $crate::__covenant_inner!(@ { $covenant } $($tail)*) + }; + + // @covenant(...), ... + (@ { $covenant:ident } @covenant($($inner:tt)*), $($tail:tt)*) => { + let inner = $crate::covenant!($($inner)*); + $covenant.push_token($crate::covenants::CovenantToken::covenant(inner)); + $crate::__covenant_inner!(@ { $covenant } $($tail)*) + }; + + // @covenant(...) + (@ { $covenant:ident } @covenant($($inner:tt)*) $(,)?) => { + $crate::__covenant_inner!(@ { $covenant } @covenant($($inner)*),) + }; + + // @arg(expr1, expr2, ...), ... + (@ { $covenant:ident } @$arg:ident($($args:expr),* $(,)?), $($tail:tt)*) => { + $covenant.push_token($crate::covenants::CovenantToken::$arg($($args),*)); + $crate::__covenant_inner!(@ { $covenant } $($tail)*) + }; + + // @arg(expr1, expr2, ...) + (@ { $covenant:ident } @$arg:ident($($args:expr),* $(,)?) $(,)?) => { + $crate::__covenant_inner!(@ { $covenant } @$arg($($args),*),) + }; + + // token(), ... + (@ { $covenant:ident } $token:ident(), $($tail:tt)*) => { + $covenant.push_token($crate::covenants::CovenantToken::$token()); + $crate::__covenant_inner!(@ { $covenant } $($tail)*) + }; + // token(filter1, filter2, ...) + (@ { $covenant:ident } $token:ident($($args:tt)+)) => { + $crate::__covenant_inner!(@ { $covenant } $token($($args)+),) + }; + + // token(filter1, filter2, ...), ... + (@ { $covenant:ident } $token:ident($($args:tt)+), $($tail:tt)*) => { + $covenant.push_token($crate::covenants::CovenantToken::$token()); + $crate::__covenant_inner!(@ { $covenant } $($args)+ $($tail)*) + }; + + // token(...) + (@ { $covenant:ident } $token:ident($($args:tt)+)) => { + $covenant.push_token($crate::covenants::CovenantToken::$token()); + $crate::__covenant_inner!(@ { $covenant } $($args)+) + }; +} + +#[cfg(test)] +mod test { + use tari_common_types::types::PublicKey; + use tari_crypto::script; + use tari_test_utils::unpack_enum; + use tari_utilities::hex::{from_hex, Hex}; + + use crate::{ + consensus::{ConsensusDecoding, ToConsensusBytes}, + covenants::{arguments::CovenantArg, filters::CovenantFilter, token::CovenantToken, Covenant}, + }; + + #[test] + fn simple() { + let covenant = covenant!(identity()); + assert_eq!(covenant.tokens().len(), 1); + assert!(matches!( + covenant.tokens()[0], + CovenantToken::Filter(CovenantFilter::Identity(_)) + )); + } + + #[test] + fn fields() { + let covenant = + covenant!(and(identity(), fields_preserved(@fields(@field::commitment, @field::sender_offset_public_key)))); + assert_eq!(covenant.to_consensus_bytes().to_hex(), "21203108020002"); + } + + #[test] + fn hash() { + let hash_str = "53563b674ba8e5166adb57afa8355bcf2ee759941eef8f8959b802367c2558bd"; + let hash_vec = from_hex(hash_str).unwrap(); + let mut hash = [0u8; 32]; + hash.copy_from_slice(hash_vec.as_slice()); + let covenant = covenant!(output_hash_eq(@hash(hash))); + assert_eq!(covenant.to_consensus_bytes().to_hex(), format!("3001{}", hash_str)); + + let covenant = covenant!(and( + identity(), + or( + identity(), + fields_preserved(@hash(hash),) + ) + )); + assert_eq!( + covenant.to_consensus_bytes().to_hex(), + "21202220310153563b674ba8e5166adb57afa8355bcf2ee759941eef8f8959b802367c2558bd" + ); + } + + #[test] + fn nested() { + let covenant = covenant!(xor( + identity(), + and(identity(), and(not(identity(),), and(identity(), identity()))) + )); + assert_eq!(covenant.to_consensus_bytes().to_hex(), "23202120212420212020"); + let h = from_hex("53563b674ba8e5166adb57afa8355bcf2ee759941eef8f8959b802367c2558bd").unwrap(); + let mut hash = [0u8; 32]; + hash.copy_from_slice(h.as_slice()); + let covenant = covenant!(and( + or( + identity(), + fields_hashed_eq( + @fields(@field::commitment, @field::features_metadata), + @hash(hash), + ), + ), + field_eq(@field::features_maturity, @uint(42)) + )); + assert_eq!( + covenant.to_consensus_bytes().to_hex(), + "21222032080200090153563b674ba8e5166adb57afa8355bcf2ee759941eef8f8959b802367c2558bd330706062a" + ); + } + + #[test] + fn covenant() { + let bytes = vec![0xba, 0xda, 0x55]; + let covenant = covenant!(field_eq(@field::covenant, @covenant(and(field_eq(@field::features_unique_id, @bytes(bytes), identity()))))); + assert_eq!(covenant.to_consensus_bytes().to_hex(), "330703050a213307070903bada5520"); + } + + #[test] + fn script() { + let hash = "53563b674ba8e5166adb57afa8355bcf2ee759941eef8f8959b802367c2558bd"; + let hash = { + let mut buf = [0u8; 32]; + buf.copy_from_slice(from_hex(hash).unwrap().as_slice()); + buf + }; + let dest_pk = PublicKey::from_hex("b0c1f788f137ba0cdc0b61e89ee43b80ebf5cca4136d3229561bf11eba347849").unwrap(); + let sender_pk = dest_pk.clone(); + let script = script!(HashSha256 PushHash(Box::new(hash)) Equal IfThen PushPubKey(Box::new(dest_pk)) Else CheckHeightVerify(100) PushPubKey(Box::new(sender_pk)) EndIf); + let covenant = covenant!(field_eq(@field::script, @script(script.clone()))); + + let decoded = Covenant::consensus_decode(&mut covenant.to_consensus_bytes().as_slice()).unwrap(); + assert_eq!(covenant, decoded); + unpack_enum!(CovenantArg::TariScript(decoded_script) = decoded.tokens()[2].as_arg().unwrap()); + assert_eq!(script, *decoded_script); + } +} diff --git a/base_layer/core/src/covenants/mod.rs b/base_layer/core/src/covenants/mod.rs new file mode 100644 index 0000000000..fff53dbfb2 --- /dev/null +++ b/base_layer/core/src/covenants/mod.rs @@ -0,0 +1,52 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! # Covenants +//! +//! Allows rules to be specified that restrict _future_ spending of subsequent transactions. +//! +//! https://rfc.tari.com/RFC-0250_Covenants.html + +mod arguments; +mod byte_codes; +mod context; +mod covenant; +mod decoder; +mod encoder; +mod error; +mod fields; +mod filters; +mod output_set; +mod token; + +pub use covenant::Covenant; +// Used in macro +#[allow(unused_imports)] +pub(self) use fields::OutputField; +#[allow(unused_imports)] +pub(self) use token::CovenantToken; + +#[macro_use] +mod macros; + +#[cfg(test)] +mod test; diff --git a/base_layer/core/src/covenants/output_set.rs b/base_layer/core/src/covenants/output_set.rs new file mode 100644 index 0000000000..f086214614 --- /dev/null +++ b/base_layer/core/src/covenants/output_set.rs @@ -0,0 +1,172 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ + cmp::Ordering, + collections::BTreeSet, + iter::FromIterator, + ops::{Deref, DerefMut}, +}; + +use crate::{covenants::error::CovenantError, transactions::transaction::TransactionOutput}; + +#[derive(Debug, Clone)] +pub struct OutputSet<'a>(BTreeSet>); + +impl<'a> OutputSet<'a> { + pub fn new(outputs: &'a [TransactionOutput]) -> Self { + // This sets the internal index for each output + // Note there is no publicly accessible way to modify the indexes + outputs.iter().enumerate().collect() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn set(&mut self, new_set: Self) { + *self = new_set; + } + + pub fn retain(&mut self, mut f: F) -> Result<(), CovenantError> + where F: FnMut(&'a TransactionOutput) -> Result { + let mut err = None; + self.0.retain(|output| match f(**output) { + Ok(b) => b, + Err(e) => { + // Theres no way to stop retain early, so keep the error for when this completes + err = Some(e); + false + }, + }); + match err { + Some(err) => Err(err), + None => Ok(()), + } + } + + pub fn union(&self, other: &Self) -> Self { + self.0.union(&other.0).copied().collect() + } + + pub fn difference(&self, other: &Self) -> Self { + self.0.difference(&other.0).copied().collect() + } + + pub fn symmetric_difference(&self, other: Self) -> Self { + self.0.symmetric_difference(&other.0).copied().collect() + } + + pub fn find_inplace(&mut self, mut pred: F) + where F: FnMut(&TransactionOutput) -> bool { + match self.0.iter().find(|indexed| pred(&**indexed)) { + Some(output) => { + let output = *output; + self.clear(); + self.0.insert(output); + }, + None => { + self.clear(); + }, + } + } + + pub fn clear(&mut self) { + self.0.clear(); + } + + #[cfg(test)] + pub(super) fn get(&self, index: usize) -> Option<&TransactionOutput> { + self.0 + .iter() + .find(|output| output.index == index) + .map(|output| **output) + } + + #[cfg(test)] + pub(super) fn get_selected_indexes(&self) -> Vec { + self.0.iter().map(|idx| idx.index).collect() + } +} + +impl<'a> FromIterator<(usize, &'a TransactionOutput)> for OutputSet<'a> { + fn from_iter>(iter: T) -> Self { + iter.into_iter().map(|(i, output)| Indexed::new(i, output)).collect() + } +} + +impl<'a> FromIterator> for OutputSet<'a> { + fn from_iter>>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} + +/// A simple wrapper struct that implements PartialEq and PartialOrd using a numeric index +#[derive(Debug, Clone, Copy)] +struct Indexed { + index: usize, + value: T, +} + +impl Indexed { + pub fn new(index: usize, value: T) -> Self { + Self { index, value } + } +} + +impl PartialEq for Indexed { + fn eq(&self, other: &Self) -> bool { + self.index == other.index + } +} + +impl Eq for Indexed {} + +impl PartialOrd for Indexed { + fn partial_cmp(&self, other: &Self) -> Option { + self.index.partial_cmp(&other.index) + } +} + +impl Ord for Indexed { + fn cmp(&self, other: &Self) -> Ordering { + self.index.cmp(&other.index) + } +} + +impl Deref for Indexed { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.value + } +} + +impl DerefMut for Indexed { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.value + } +} diff --git a/base_layer/core/src/covenants/test.rs b/base_layer/core/src/covenants/test.rs new file mode 100644 index 0000000000..f881bd7445 --- /dev/null +++ b/base_layer/core/src/covenants/test.rs @@ -0,0 +1,52 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::iter; + +use crate::{ + covenants::{context::CovenantContext, Covenant}, + transactions::{ + test_helpers::{TestParams, UtxoTestParams}, + transaction::{TransactionInput, TransactionOutput}, + }, +}; + +pub fn create_outputs(n: usize, utxo_params: UtxoTestParams) -> Vec { + iter::repeat_with(|| { + let params = TestParams::new(); + let output = params.create_unblinded_output(utxo_params.clone()); + output.as_transaction_output(&Default::default()).unwrap() + }) + .take(n) + .collect() +} + +pub fn create_input() -> TransactionInput { + let params = TestParams::new(); + let output = params.create_unblinded_output(Default::default()); + output.as_transaction_input(&Default::default()).unwrap() +} + +pub fn create_context<'a>(covenant: &Covenant, input: &'a TransactionInput, block_height: u64) -> CovenantContext<'a> { + let tokens = covenant.tokens().to_vec(); + CovenantContext::new(tokens.into(), input, block_height) +} diff --git a/base_layer/core/src/covenants/token.rs b/base_layer/core/src/covenants/token.rs new file mode 100644 index 0000000000..d93e12e2d5 --- /dev/null +++ b/base_layer/core/src/covenants/token.rs @@ -0,0 +1,220 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{collections::VecDeque, io, iter::FromIterator}; + +use tari_common_types::types::{Commitment, PublicKey}; +use tari_crypto::script::TariScript; + +use crate::covenants::{ + arguments::{CovenantArg, Hash}, + decoder::{CovenantDecodeError, CovenentReadExt}, + fields::OutputField, + filters::{ + AbsoluteHeightFilter, + AndFilter, + CovenantFilter, + FieldEqFilter, + FieldsHashedEqFilter, + FieldsPreservedFilter, + IdentityFilter, + NotFilter, + OrFilter, + OutputHashEqFilter, + XorFilter, + }, + Covenant, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CovenantToken { + Filter(CovenantFilter), + Arg(CovenantArg), +} + +impl CovenantToken { + pub fn read_from(reader: &mut R) -> Result, CovenantDecodeError> { + let code = match reader.read_next_byte_code()? { + Some(c) => c, + // Nothing further to read + None => return Ok(None), + }; + match code { + code if CovenantFilter::is_valid_code(code) => { + let filter = CovenantFilter::try_from_byte_code(code)?; + Ok(Some(CovenantToken::Filter(filter))) + }, + code if CovenantArg::is_valid_code(code) => { + let arg = CovenantArg::read_from(reader, code)?; + Ok(Some(CovenantToken::Arg(arg))) + }, + code => Err(CovenantDecodeError::UnknownByteCode { code }), + } + } + + pub fn write_to(&self, writer: &mut W) -> Result { + match self { + CovenantToken::Filter(filter) => filter.write_to(writer), + CovenantToken::Arg(arg) => arg.write_to(writer), + } + } + + pub fn as_filter(&self) -> Option<&CovenantFilter> { + match self { + CovenantToken::Filter(filter) => Some(filter), + CovenantToken::Arg(_) => None, + } + } + + pub fn as_arg(&self) -> Option<&CovenantArg> { + match self { + CovenantToken::Filter(_) => None, + CovenantToken::Arg(arg) => Some(arg), + } + } + + //---------------------------------- Macro helper functions --------------------------------------------// + + #[allow(dead_code)] + pub(super) fn identity() -> Self { + CovenantToken::Filter(CovenantFilter::Identity(IdentityFilter)) + } + + #[allow(dead_code)] + pub(super) fn and() -> Self { + CovenantToken::Filter(CovenantFilter::And(AndFilter)) + } + + #[allow(dead_code)] + pub(super) fn or() -> Self { + CovenantToken::Filter(CovenantFilter::Or(OrFilter)) + } + + #[allow(dead_code)] + pub(super) fn xor() -> Self { + CovenantToken::Filter(CovenantFilter::Xor(XorFilter)) + } + + #[allow(dead_code)] + pub(super) fn not() -> Self { + CovenantToken::Filter(CovenantFilter::Not(NotFilter)) + } + + #[allow(dead_code)] + pub(super) fn output_hash_eq() -> Self { + CovenantToken::Filter(CovenantFilter::OutputHashEq(OutputHashEqFilter)) + } + + #[allow(dead_code)] + pub(super) fn fields_preserved() -> Self { + CovenantToken::Filter(CovenantFilter::FieldsPreserved(FieldsPreservedFilter)) + } + + #[allow(dead_code)] + pub(super) fn field_eq() -> Self { + CovenantToken::Filter(CovenantFilter::FieldEq(FieldEqFilter)) + } + + #[allow(dead_code)] + pub(super) fn fields_hashed_eq() -> Self { + CovenantToken::Filter(CovenantFilter::FieldsHashedEq(FieldsHashedEqFilter)) + } + + #[allow(dead_code)] + pub(super) fn absolute_height() -> Self { + CovenantToken::Filter(CovenantFilter::AbsoluteHeight(AbsoluteHeightFilter)) + } + + #[allow(dead_code)] + pub(super) fn hash(hash: Hash) -> Self { + CovenantToken::Arg(CovenantArg::Hash(hash)) + } + + #[allow(dead_code)] + pub(super) fn public_key(public_key: PublicKey) -> Self { + CovenantToken::Arg(CovenantArg::PublicKey(public_key)) + } + + #[allow(dead_code)] + pub(super) fn commitment(commitment: Commitment) -> Self { + CovenantToken::Arg(CovenantArg::Commitment(commitment)) + } + + #[allow(dead_code)] + pub(super) fn script(script: TariScript) -> Self { + CovenantToken::Arg(CovenantArg::TariScript(script)) + } + + #[allow(dead_code)] + pub(super) fn covenant(covenant: Covenant) -> Self { + CovenantToken::Arg(CovenantArg::Covenant(covenant)) + } + + #[allow(dead_code)] + pub(super) fn uint(val: u64) -> Self { + CovenantToken::Arg(CovenantArg::Uint(val)) + } + + #[allow(dead_code)] + pub(super) fn field(field: OutputField) -> Self { + CovenantToken::Arg(CovenantArg::OutputField(field)) + } + + #[allow(dead_code)] + pub(super) fn fields(fields: Vec) -> Self { + CovenantToken::Arg(CovenantArg::OutputFields(fields.into())) + } + + #[allow(dead_code)] + pub(super) fn bytes(bytes: Vec) -> Self { + CovenantToken::Arg(CovenantArg::Bytes(bytes)) + } +} + +#[derive(Debug, Clone, Default)] +pub struct CovenantTokenCollection { + tokens: VecDeque, +} + +impl CovenantTokenCollection { + pub fn is_empty(&self) -> bool { + self.tokens.is_empty() + } + + pub fn next(&mut self) -> Option { + self.tokens.pop_front() + } +} + +impl FromIterator for CovenantTokenCollection { + fn from_iter>(iter: T) -> Self { + Self { + tokens: iter.into_iter().collect(), + } + } +} + +impl From> for CovenantTokenCollection { + fn from(tokens: Vec) -> Self { + Self { tokens: tokens.into() } + } +} diff --git a/base_layer/core/src/lib.rs b/base_layer/core/src/lib.rs index 8d4a080483..d4fe3dc091 100644 --- a/base_layer/core/src/lib.rs +++ b/base_layer/core/src/lib.rs @@ -19,7 +19,6 @@ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - #![cfg_attr(not(debug_assertions), deny(unused_variables))] #![cfg_attr(not(debug_assertions), deny(unused_imports))] #![cfg_attr(not(debug_assertions), deny(dead_code))] @@ -35,6 +34,8 @@ pub mod blocks; #[cfg(feature = "base_node")] pub mod chain_storage; pub mod consensus; +#[macro_use] +pub mod covenants; #[cfg(feature = "base_node")] pub mod iterators; pub mod proof_of_work; diff --git a/base_layer/core/src/transactions/test_helpers.rs b/base_layer/core/src/transactions/test_helpers.rs index ef7abab220..cf4304ec4e 100644 --- a/base_layer/core/src/transactions/test_helpers.rs +++ b/base_layer/core/src/transactions/test_helpers.rs @@ -36,7 +36,7 @@ use tari_crypto::{ }; use crate::{ - consensus::{ConsensusEncodingSized, ConsensusEncodingWrapper, ConsensusManager}, + consensus::{ConsensusEncodingSized, ConsensusManager}, transactions::{ crypto_factories::CryptoFactories, fee::Fee, @@ -179,8 +179,7 @@ impl TestParams { pub fn get_size_for_default_metadata(&self, num_outputs: usize) -> usize { self.fee().weighting().round_up_metadata_size( - ConsensusEncodingWrapper::wrap(&script![Nop]).consensus_encode_exact_size() + - OutputFeatures::default().consensus_encode_exact_size(), + script![Nop].consensus_encode_exact_size() + OutputFeatures::default().consensus_encode_exact_size(), ) * num_outputs } } @@ -352,8 +351,7 @@ pub struct TransactionSchema { fn default_metadata_byte_size() -> usize { TransactionWeight::latest().round_up_metadata_size( - OutputFeatures::default().consensus_encode_exact_size() + - ConsensusEncodingWrapper::wrap(&script![Nop]).consensus_encode_exact_size(), + OutputFeatures::default().consensus_encode_exact_size() + script![Nop].consensus_encode_exact_size(), ) } diff --git a/base_layer/core/src/transactions/transaction/output_features.rs b/base_layer/core/src/transactions/transaction/output_features.rs index 12b3e63d8e..0c7ef7ec1e 100644 --- a/base_layer/core/src/transactions/transaction/output_features.rs +++ b/base_layer/core/src/transactions/transaction/output_features.rs @@ -28,7 +28,6 @@ use std::{ io::{Read, Write}, }; -use integer_encoding::{VarInt, VarIntReader, VarIntWriter}; use serde::{Deserialize, Serialize}; use tari_common_types::types::{Commitment, PublicKey}; use tari_utilities::ByteArray; @@ -174,8 +173,8 @@ impl OutputFeatures { impl ConsensusEncoding for OutputFeatures { fn consensus_encode(&self, writer: &mut W) -> Result { - let mut written = writer.write_varint(Self::CONSENSUS_ENCODING_VERSION)?; - written += writer.write_varint(self.maturity)?; + let mut written = Self::CONSENSUS_ENCODING_VERSION.consensus_encode(writer)?; + written += self.maturity.consensus_encode(writer)?; written += self.flags.consensus_encode(writer)?; Ok(written) } @@ -183,16 +182,16 @@ impl ConsensusEncoding for OutputFeatures { impl ConsensusEncodingSized for OutputFeatures { fn consensus_encode_exact_size(&self) -> usize { - Self::CONSENSUS_ENCODING_VERSION.required_space() + + Self::CONSENSUS_ENCODING_VERSION.consensus_encode_exact_size() + self.flags.consensus_encode_exact_size() + - self.maturity.required_space() + self.maturity.consensus_encode_exact_size() } } impl ConsensusDecoding for OutputFeatures { fn consensus_decode(reader: &mut R) -> Result { // Changing the order of these operations is consensus breaking - let version = reader.read_varint::()?; + let version = u8::consensus_decode(reader)?; if version != Self::CONSENSUS_ENCODING_VERSION { return Err(io::Error::new( io::ErrorKind::InvalidInput, @@ -203,8 +202,8 @@ impl ConsensusDecoding for OutputFeatures { ), )); } - // Decode safety: read_varint will stop reading the varint after 10 bytes - let maturity = reader.read_varint()?; + // Decode safety: consensus_decode will stop reading the varint after 10 bytes + let maturity = u64::consensus_decode(reader)?; let flags = OutputFlags::consensus_decode(reader)?; Ok(Self { flags, diff --git a/base_layer/core/src/transactions/transaction/transaction_input.rs b/base_layer/core/src/transactions/transaction/transaction_input.rs index cc3930bb19..6dfecdccc3 100644 --- a/base_layer/core/src/transactions/transaction/transaction_input.rs +++ b/base_layer/core/src/transactions/transaction/transaction_input.rs @@ -148,6 +148,13 @@ impl TransactionInput { } } + pub fn features_mut(&mut self) -> Result<&mut OutputFeatures, TransactionError> { + match self.spent_output { + SpentOutput::OutputHash(_) => Err(TransactionError::MissingTransactionInputData), + SpentOutput::OutputData { ref mut features, .. } => Ok(features), + } + } + pub fn script(&self) -> Result<&TariScript, TransactionError> { match self.spent_output { SpentOutput::OutputHash(_) => Err(TransactionError::MissingTransactionInputData), diff --git a/base_layer/core/src/transactions/transaction/transaction_output.rs b/base_layer/core/src/transactions/transaction/transaction_output.rs index 2ae7dc8869..3b5370d6e7 100644 --- a/base_layer/core/src/transactions/transaction/transaction_output.rs +++ b/base_layer/core/src/transactions/transaction/transaction_output.rs @@ -54,7 +54,7 @@ use tari_crypto::{ }; use crate::{ - consensus::{ConsensusEncodingSized, ConsensusEncodingWrapper}, + consensus::ConsensusEncodingSized, transactions::{ tari_amount::MicroTari, transaction, @@ -316,8 +316,7 @@ impl TransactionOutput { } pub fn get_metadata_size(&self) -> usize { - self.features.consensus_encode_exact_size() + - ConsensusEncodingWrapper::wrap(&self.script).consensus_encode_exact_size() + self.features.consensus_encode_exact_size() + self.script.consensus_encode_exact_size() } } diff --git a/base_layer/core/src/transactions/transaction/unblinded_output.rs b/base_layer/core/src/transactions/transaction/unblinded_output.rs index f291fc375b..21e11e92ce 100644 --- a/base_layer/core/src/transactions/transaction/unblinded_output.rs +++ b/base_layer/core/src/transactions/transaction/unblinded_output.rs @@ -37,7 +37,7 @@ use tari_crypto::{ }; use crate::{ - consensus::{ConsensusEncodingSized, ConsensusEncodingWrapper}, + consensus::ConsensusEncodingSized, transactions::{ tari_amount::MicroTari, transaction, @@ -207,8 +207,7 @@ impl UnblindedOutput { } pub fn metadata_byte_size(&self) -> usize { - self.features.consensus_encode_exact_size() + - ConsensusEncodingWrapper::wrap(&self.script).consensus_encode_exact_size() + self.features.consensus_encode_exact_size() + self.script.consensus_encode_exact_size() } // Note: The Hashable trait is not used here due to the dependency on `CryptoFactories`, and `commitment` us not diff --git a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs index 948d2aa853..87ae83f90a 100644 --- a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs +++ b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs @@ -41,7 +41,7 @@ use tari_crypto::{ }; use crate::{ - consensus::{ConsensusConstants, ConsensusEncodingSized, ConsensusEncodingWrapper}, + consensus::{ConsensusConstants, ConsensusEncodingSized}, transactions::{ crypto_factories::CryptoFactories, fee::Fee, @@ -281,8 +281,7 @@ impl SenderTransactionInitializer { .iter() .map(|o| { self.fee.weighting().round_up_metadata_size( - o.features.consensus_encode_exact_size() + - ConsensusEncodingWrapper::wrap(&o.script).consensus_encode_exact_size(), + o.features.consensus_encode_exact_size() + o.script.consensus_encode_exact_size(), ) }) .sum::(); @@ -296,7 +295,7 @@ impl SenderTransactionInitializer { .map(|script| { self.fee.weighting().round_up_metadata_size( self.get_recipient_output_features().consensus_encode_exact_size() + - ConsensusEncodingWrapper::wrap(script).consensus_encode_exact_size(), + script.consensus_encode_exact_size(), ) }) .sum::(); @@ -337,7 +336,7 @@ impl SenderTransactionInitializer { let change_metadata_size = self .change_script .as_ref() - .map(|script| ConsensusEncodingWrapper::wrap(script).consensus_encode_exact_size()) + .map(|script| script.consensus_encode_exact_size()) .unwrap_or(0) + output_features.consensus_encode_exact_size(); let change_metadata_size = self.fee().weighting().round_up_metadata_size(change_metadata_size); diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index 6231383bcb..bed2a1b805 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -36,13 +36,7 @@ use tari_crypto::{ use crate::{ blocks::{Block, BlockHeader, BlockHeaderValidationError, BlockValidationError}, chain_storage::{BlockchainBackend, MmrRoots, MmrTree}, - consensus::{ - emission::Emission, - ConsensusConstants, - ConsensusEncodingSized, - ConsensusEncodingWrapper, - ConsensusManager, - }, + consensus::{emission::Emission, ConsensusConstants, ConsensusEncodingSized, ConsensusManager}, proof_of_work::{ monero_difficulty, monero_rx::MoneroPowData, @@ -491,7 +485,7 @@ pub fn check_outputs( /// Checks the byte size of TariScript is less than or equal to the given size, otherwise returns an error. pub fn check_tari_script_byte_size(script: &TariScript, max_script_size: usize) -> Result<(), ValidationError> { - let script_size = ConsensusEncodingWrapper::wrap(script).consensus_encode_exact_size(); + let script_size = script.consensus_encode_exact_size(); if script_size > max_script_size { return Err(ValidationError::TariScriptExceedsMaxSize { max_script_size, diff --git a/base_layer/core/tests/node_service.rs b/base_layer/core/tests/node_service.rs index 23a784775b..bed9fbdb07 100644 --- a/base_layer/core/tests/node_service.rs +++ b/base_layer/core/tests/node_service.rs @@ -500,6 +500,7 @@ async fn local_get_new_block_template_and_get_new_block() { } #[tokio::test] +#[ignore = "0-conf regression fixed in #3680"] async fn local_get_new_block_with_zero_conf() { let factories = CryptoFactories::default(); let temp_dir = tempdir().unwrap(); @@ -577,6 +578,7 @@ async fn local_get_new_block_with_zero_conf() { } #[tokio::test] +#[ignore = "0-conf regression fixed in #3680"] async fn local_get_new_block_with_combined_transaction() { let factories = CryptoFactories::default(); let temp_dir = tempdir().unwrap(); diff --git a/base_layer/p2p/src/initialization.rs b/base_layer/p2p/src/initialization.rs index ee04b6ab4b..29e12178a4 100644 --- a/base_layer/p2p/src/initialization.rs +++ b/base_layer/p2p/src/initialization.rs @@ -105,7 +105,7 @@ impl CommsInitializationError { => r#"Unable to connect to the Tor control port. Please check that you have the Tor proxy running and that access to the Tor control port is turned on. If you are unsure of what to do, use the following command to start the Tor proxy: - tor --allow-missing-torrc --ignore-missing-torrc --clientonly 1 --socksport 9050 --controlport 127.0.0.1:9051 --log "notice stdout" --clientuseipv6 1"# + tor --allow-missing-torrc --ignore-missing-torrc --clientonly 1 --socksport 9050 --controlport 127.0.0.1:9051 --log "warn stdout" --clientuseipv6 1"# .to_string(), err => format!("Failed to initialize comms: {:?}", err), } diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 0c924ce873..1f539387a9 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -33,7 +33,7 @@ use tari_common_types::{ }; use tari_comms::{types::CommsPublicKey, NodeIdentity}; use tari_core::{ - consensus::{ConsensusConstants, ConsensusEncodingSized, ConsensusEncodingWrapper}, + consensus::{ConsensusConstants, ConsensusEncodingSized}, proto::base_node::FetchMatchingUtxos, transactions::{ fee::Fee, @@ -659,8 +659,7 @@ where .consensus_constants .transaction_weight() .round_up_metadata_size( - OutputFeatures::default().consensus_encode_exact_size() + - ConsensusEncodingWrapper::wrap(&script![Nop]).consensus_encode_exact_size(), + OutputFeatures::default().consensus_encode_exact_size() + script![Nop].consensus_encode_exact_size(), ); let utxo_selection = self @@ -709,8 +708,7 @@ where .consensus_constants .transaction_weight() .round_up_metadata_size( - output_features.consensus_encode_exact_size() + - ConsensusEncodingWrapper::wrap(&recipient_script).consensus_encode_exact_size(), + output_features.consensus_encode_exact_size() + recipient_script.consensus_encode_exact_size(), ); let input_selection = self @@ -909,7 +907,10 @@ where total + weighting.round_up_metadata_size( output.features.consensus_encode_exact_size() + - ConsensusEncodingWrapper::wrap(output.script.as_ref().unwrap_or(&nop_script)) + output + .script + .as_ref() + .unwrap_or(&nop_script) .consensus_encode_exact_size(), ) }); @@ -1070,8 +1071,7 @@ where .consensus_constants .transaction_weight() .round_up_metadata_size( - output_features.consensus_encode_exact_size() + - ConsensusEncodingWrapper::wrap(&script).consensus_encode_exact_size(), + output_features.consensus_encode_exact_size() + script.consensus_encode_exact_size(), ); let input_selection = self @@ -1310,8 +1310,7 @@ where // Assumes that default Outputfeatures are used for change utxo let default_metadata_size = fee_calc.weighting().round_up_metadata_size( - OutputFeatures::default().consensus_encode_exact_size() + - ConsensusEncodingWrapper::wrap(&script![Nop]).consensus_encode_exact_size(), + OutputFeatures::default().consensus_encode_exact_size() + script![Nop].consensus_encode_exact_size(), ); let mut requires_change_output = false; for o in uo { @@ -1395,8 +1394,7 @@ where .consensus_constants .transaction_weight() .round_up_metadata_size( - output_features.consensus_encode_exact_size() + - ConsensusEncodingWrapper::wrap(&script).consensus_encode_exact_size(), + output_features.consensus_encode_exact_size() + script.consensus_encode_exact_size(), ); let total_split_amount = amount_per_split * split_count as u64; diff --git a/base_layer/wallet/tests/output_manager_service_tests/service.rs b/base_layer/wallet/tests/output_manager_service_tests/service.rs index a1bde049c0..eb8e939611 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/service.rs @@ -34,7 +34,7 @@ use tari_comms::{ use tari_core::{ base_node::rpc::BaseNodeWalletRpcServer, blocks::BlockHeader, - consensus::{ConsensusConstantsBuilder, ConsensusEncodingSized, ConsensusEncodingWrapper}, + consensus::{ConsensusConstantsBuilder, ConsensusEncodingSized}, proto::base_node::{QueryDeletedResponse, UtxoQueryResponse, UtxoQueryResponses}, transactions::{ fee::Fee, @@ -93,8 +93,7 @@ use crate::support::{ fn default_metadata_byte_size() -> usize { TransactionWeight::latest().round_up_metadata_size( - OutputFeatures::default().consensus_encode_exact_size() + - ConsensusEncodingWrapper::wrap(&script![Nop]).consensus_encode_exact_size(), + OutputFeatures::default().consensus_encode_exact_size() + script![Nop].consensus_encode_exact_size(), ) } diff --git a/buildtools/create_ubuntu_install_zip.sh b/buildtools/create_ubuntu_install_zip.sh index dfe93a0961..a3ca3672f6 100755 --- a/buildtools/create_ubuntu_install_zip.sh +++ b/buildtools/create_ubuntu_install_zip.sh @@ -42,33 +42,33 @@ else fi # One click miner -cp -f -P "${app_dir}/tari_base_node/ubuntu/start_all" "${tarball_folder}/start_all" -cp -f "${app_dir}/tari_base_node/ubuntu/runtime/start_all.sh" "${tarball_folder}/runtime/start_all.sh" +cp -f -P "${app_dir}/tari_base_node/linux/start_all" "${tarball_folder}/start_all" +cp -f "${app_dir}/tari_base_node/linux/runtime/start_all.sh" "${tarball_folder}/runtime/start_all.sh" # Base Node -cp -f -P "${app_dir}/tari_base_node/ubuntu/setup_tor_service" "${tarball_folder}/setup_tor_service" -cp -f -P "${app_dir}/tari_base_node/ubuntu/start_tari_base_node" "${tarball_folder}/start_tari_base_node" -cp -f -P "${app_dir}/tari_base_node/ubuntu/start_tor" "${tarball_folder}/start_tor" -cp -f "${app_dir}/tari_base_node/ubuntu/runtime/setup_tor_service.sh" "${tarball_folder}/runtime/setup_tor_service.sh" -cp -f "${app_dir}/tari_base_node/ubuntu/runtime/start_tari_base_node.sh" "${tarball_folder}/runtime/start_tari_base_node.sh" -cp -f "${app_dir}/tari_base_node/ubuntu/runtime/start_tor.sh" "${tarball_folder}/runtime/start_tor.sh" +cp -f -P "${app_dir}/tari_base_node/linux/setup_tor_service" "${tarball_folder}/setup_tor_service" +cp -f -P "${app_dir}/tari_base_node/linux/start_tari_base_node" "${tarball_folder}/start_tari_base_node" +cp -f -P "${app_dir}/tari_base_node/linux/start_tor" "${tarball_folder}/start_tor" +cp -f "${app_dir}/tari_base_node/linux/runtime/setup_tor_service.sh" "${tarball_folder}/runtime/setup_tor_service.sh" +cp -f "${app_dir}/tari_base_node/linux/runtime/start_tari_base_node.sh" "${tarball_folder}/runtime/start_tari_base_node.sh" +cp -f "${app_dir}/tari_base_node/linux/runtime/start_tor.sh" "${tarball_folder}/runtime/start_tor.sh" cp -f "${project_dir}/target/release/tari_base_node" "${tarball_folder}/runtime/tari_base_node" # Console Wallet -cp -f -P "${app_dir}/tari_console_wallet/ubuntu/start_tari_console_wallet" "${tarball_folder}/start_tari_console_wallet" -cp -f "${app_dir}/tari_console_wallet/ubuntu/runtime/start_tari_console_wallet.sh" "${tarball_folder}/runtime/start_tari_console_wallet.sh" +cp -f -P "${app_dir}/tari_console_wallet/linux/start_tari_console_wallet" "${tarball_folder}/start_tari_console_wallet" +cp -f "${app_dir}/tari_console_wallet/linux/runtime/start_tari_console_wallet.sh" "${tarball_folder}/runtime/start_tari_console_wallet.sh" cp -f "${project_dir}/target/release/tari_console_wallet" "${tarball_folder}/runtime/tari_console_wallet" # Mining Node -cp -f -P "${app_dir}/tari_mining_node/ubuntu/start_tari_mining_node" "${tarball_folder}/start_tari_mining_node" -cp -f "${app_dir}/tari_mining_node/ubuntu/runtime/start_tari_mining_nodet.sh" "${tarball_folder}/runtime/start_tari_mining_node.sh" +cp -f -P "${app_dir}/tari_mining_node/linux/start_tari_mining_node" "${tarball_folder}/start_tari_mining_node" +cp -f "${app_dir}/tari_mining_node/linux/runtime/start_tari_mining_nodet.sh" "${tarball_folder}/runtime/start_tari_mining_node.sh" cp -f "${project_dir}/target/release/tari_mining_node" "${tarball_folder}/runtime/tari_mining_node" # Merge Mining Proxy -cp -f -P "${app_dir}/tari_merge_mining_proxy/ubuntu/start_tari_merge_mining_proxy" "${tarball_folder}/start_tari_merge_mining_proxy" -cp -f -P "${app_dir}/tari_merge_mining_proxy/ubuntu/start_xmrig" "${tarball_folder}/start_xmrig" -cp -f "${app_dir}/tari_merge_mining_proxy/ubuntu/runtime/start_tari_merge_mining_proxy.sh" "${tarball_folder}/runtime/start_tari_merge_mining_proxy.sh" -cp -f "${app_dir}/tari_merge_mining_proxy/ubuntu/runtime/start_xmrig.sh" "${tarball_folder}/runtime/start_xmrig.sh" +cp -f -P "${app_dir}/tari_merge_mining_proxy/linux/start_tari_merge_mining_proxy" "${tarball_folder}/start_tari_merge_mining_proxy" +cp -f -P "${app_dir}/tari_merge_mining_proxy/linux/start_xmrig" "${tarball_folder}/start_xmrig" +cp -f "${app_dir}/tari_merge_mining_proxy/linux/runtime/start_tari_merge_mining_proxy.sh" "${tarball_folder}/runtime/start_tari_merge_mining_proxy.sh" +cp -f "${app_dir}/tari_merge_mining_proxy/linux/runtime/start_xmrig.sh" "${tarball_folder}/runtime/start_xmrig.sh" cp -f "${project_dir}/target/release/tari_merge_mining_proxy" "${tarball_folder}/runtime/tari_merge_mining_proxy" # 3rd party install diff --git a/common/src/exit_codes.rs b/common/src/exit_codes.rs index 74be3eb6ab..da5a33401f 100644 --- a/common/src/exit_codes.rs +++ b/common/src/exit_codes.rs @@ -70,7 +70,7 @@ impl ExitCodes { eprintln!("If you are unsure of what to do, use the following command to start the Tor proxy:"); eprintln!( "tor --allow-missing-torrc --ignore-missing-torrc --clientonly 1 --socksport 9050 --controlport \ - 127.0.0.1:9051 --log \"notice stdout\" --clientuseipv6 1", + 127.0.0.1:9051 --log \"warn stdout\" --clientuseipv6 1", ); },