Repository: ergoplatform/sigma-rust (language: rust)

Issue:
[50 SigmaUSD] Construct `ErgoStateContext` from headers in ergo-lib and Wasm
Remove `*::dummy` methods (use `force_any_val` in tests).


Discussion / hints:
Hi, I'm starting on this one

Fix patch:
diff --git a/bindings/ergo-lib-wasm/src/block_header.rs b/bindings/ergo-lib-wasm/src/block_header.rs
index 054dd8279..433d20919 100644
--- a/bindings/ergo-lib-wasm/src/block_header.rs
+++ b/bindings/ergo-lib-wasm/src/block_header.rs
@@ -7,6 +7,8 @@ extern crate derive_more;
 use derive_more::{From, Into};
 
 use crate::error_conversion::to_js;
+use ergo_lib::chain::ergo_state_context::Headers;
+use std::convert::TryInto;
 
 /// Block header
 #[wasm_bindgen]
@@ -87,3 +89,11 @@ impl From<BlockHeaders> for Vec<Header> {
         bs.0.into_iter().map(Header::from).collect()
     }
 }
+
+impl From<BlockHeaders> for Headers {
+    fn from(bs: BlockHeaders) -> Self {
+        let headers: Vec<Header> = bs.0.into_iter().map(Header::from).collect();
+
+        headers.try_into().unwrap()
+    }
+}
diff --git a/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs b/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs
index 77511b5fb..8a130fd5d 100644
--- a/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs
@@ -5,6 +5,7 @@ use wasm_bindgen::prelude::*;
 extern crate derive_more;
 use derive_more::{From, Into};
 
+use crate::block_header::BlockHeaders;
 use crate::header::PreHeader;
 
 /// Blockchain state (last headers, etc.)
@@ -16,14 +17,18 @@ pub struct ErgoStateContext(chain::ergo_state_context::ErgoStateContext);
 impl ErgoStateContext {
     /// Create new context from pre-header
     #[wasm_bindgen(constructor)]
-    pub fn new(pre_header: PreHeader) -> Self {
-        let mut ergo_state_context = chain::ergo_state_context::ErgoStateContext::dummy();
-        ergo_state_context.pre_header = pre_header.into();
-        ergo_state_context.into()
-    }
-
-    /// empty (dummy) context (for signing P2PK tx only)
-    pub fn dummy() -> ErgoStateContext {
-        ErgoStateContext(chain::ergo_state_context::ErgoStateContext::dummy())
+    pub fn new(pre_header: PreHeader, headers: BlockHeaders) -> Result<ErgoStateContext, JsValue> {
+        match headers.len() {
+            10 => Ok(chain::ergo_state_context::ErgoStateContext::new(
+                pre_header.into(),
+                headers.into(),
+            )
+            .into()),
+            h => Err(js_sys::Error::new(&format!(
+                "Not enough block headers, expected 10 but got {}",
+                h
+            ))
+            .into()),
+        }
     }
 }
diff --git a/ergo-lib/src/chain/ergo_state_context.rs b/ergo-lib/src/chain/ergo_state_context.rs
index ec4474889..07b629387 100644
--- a/ergo-lib/src/chain/ergo_state_context.rs
+++ b/ergo-lib/src/chain/ergo_state_context.rs
@@ -1,9 +1,10 @@
 //! Blockchain state
-use std::convert::TryInto;
-
 use ergotree_ir::chain::header::Header;
 use ergotree_ir::chain::preheader::PreHeader;
 
+/// Fixed number of last block headers in descending order (first header is the newest one)
+pub type Headers = [Header; 10];
+
 /// Blockchain state (last headers, etc.)
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct ErgoStateContext {
@@ -11,18 +12,32 @@ pub struct ErgoStateContext {
     /// by a miner before it's formation
     pub pre_header: PreHeader,
     /// Fixed number of last block headers in descending order (first header is the newest one)
-    pub headers: [Header; 10],
+    pub headers: Headers,
 }
 
 impl ErgoStateContext {
-    /// Dummy instance intended for tests where actual values are not used
-    pub fn dummy() -> ErgoStateContext {
-        let headers = vec![Header::dummy(); 10]
-            .try_into()
-            .expect("internal error: Headers array length isn't eq to 10");
+    /// Create an ErgoStateContext instance
+    pub fn new(pre_header: PreHeader, headers: Headers) -> ErgoStateContext {
         ErgoStateContext {
-            pre_header: PreHeader::dummy(),
+            pre_header,
             headers,
         }
     }
 }
+
+#[cfg(feature = "arbitrary")]
+mod arbitrary {
+    use super::*;
+    use proptest::prelude::*;
+
+    impl Arbitrary for ErgoStateContext {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (any::<PreHeader>(), any::<Headers>())
+                .prop_map(|(pre_header, headers)| Self::new(pre_header, headers))
+                .boxed()
+        }
+    }
+}
diff --git a/ergo-lib/src/wallet/signing.rs b/ergo-lib/src/wallet/signing.rs
index 6cb9ab74e..9f82ff81b 100644
--- a/ergo-lib/src/wallet/signing.rs
+++ b/ergo-lib/src/wallet/signing.rs
@@ -237,10 +237,10 @@ mod tests {
                 None, output_candidates.try_into().unwrap()).unwrap();
             let tx_context = TransactionContext { spending_tx: tx,
                                                   boxes_to_spend: boxes_to_spend.clone(), data_boxes: vec![] };
-            let res = sign_transaction(prover.as_ref(), tx_context.clone(), &ErgoStateContext::dummy());
+            let res = sign_transaction(prover.as_ref(), tx_context.clone(), &force_any_val::<ErgoStateContext>());
             let signed_tx = res.unwrap();
             prop_assert!(verify_tx_proofs(&signed_tx, &boxes_to_spend).unwrap());
-            let reduced_tx = reduce_tx(tx_context, &ErgoStateContext::dummy()).unwrap();
+            let reduced_tx = reduce_tx(tx_context, &force_any_val::<ErgoStateContext>()).unwrap();
             let signed_reduced_tx = sign_reduced_transaction(prover.as_ref(), reduced_tx).unwrap();
             prop_assert!(verify_tx_proofs(&signed_reduced_tx, &boxes_to_spend).unwrap());
         }
diff --git a/ergotree-ir/src/chain/header.rs b/ergotree-ir/src/chain/header.rs
index 773c28eda..8e1f703a3 100644
--- a/ergotree-ir/src/chain/header.rs
+++ b/ergotree-ir/src/chain/header.rs
@@ -61,30 +61,6 @@ pub struct Header {
     pub votes: Votes,
 }
 
-impl Header {
-    /// Dummy instance intended for tests where actual values are not used
-    pub fn dummy() -> Self {
-        let empty_digest = Digest32::zero();
-        Header {
-            version: 1,
-            id: BlockId(empty_digest.clone()),
-            parent_id: BlockId(empty_digest.clone()),
-            ad_proofs_root: empty_digest.clone(),
-            state_root: ADDigest::zero(),
-            transaction_root: empty_digest.clone(),
-            timestamp: 0,
-            n_bits: 0,
-            height: 0,
-            extension_root: empty_digest,
-            miner_pk: dlog_group::generator().into(),
-            pow_onetime_pk: dlog_group::generator().into(),
-            nonce: Vec::new(),
-            pow_distance: BigInt::default(),
-            votes: Votes([0u8; 3]),
-        }
-    }
-}
-
 impl From<Header> for PreHeader {
     fn from(bh: Header) -> Self {
         PreHeader {
diff --git a/ergotree-ir/src/chain/preheader.rs b/ergotree-ir/src/chain/preheader.rs
index 7df0ac58e..8305e192c 100644
--- a/ergotree-ir/src/chain/preheader.rs
+++ b/ergotree-ir/src/chain/preheader.rs
@@ -2,7 +2,7 @@
 
 use crate::sigma_protocol::dlog_group;
 
-use super::{block_id::BlockId, digest32::Digest32, votes::Votes};
+use super::{block_id::BlockId, votes::Votes};
 
 /// Block header with the current `spendingTransaction`, that can be predicted
 /// by a miner before it's formation
@@ -24,21 +24,6 @@ pub struct PreHeader {
     pub votes: Votes,
 }
 
-impl PreHeader {
-    /// Dummy instance intended for tests where actual values are not used
-    pub fn dummy() -> Self {
-        PreHeader {
-            version: 1,
-            parent_id: BlockId(Digest32::zero()),
-            timestamp: 0,
-            n_bits: 0,
-            height: 0,
-            miner_pk: dlog_group::generator().into(),
-            votes: Votes([0u8; 3]),
-        }
-    }
-}
-
 #[cfg(feature = "arbitrary")]
 mod arbitrary {
     use proptest::array::{uniform3, uniform32};


Test patch:
diff --git a/bindings/ergo-lib-wasm/tests/test_transaction.js b/bindings/ergo-lib-wasm/tests/test_transaction.js
index 4600da3dd..b2b36f642 100644
--- a/bindings/ergo-lib-wasm/tests/test_transaction.js
+++ b/bindings/ergo-lib-wasm/tests/test_transaction.js
@@ -1,5 +1,7 @@
 import { expect, assert } from 'chai';
 
+import { generate_block_headers } from './utils';
+
 import {
   Address, Wallet, ErgoBox, ErgoBoxCandidateBuilder, Contract,
   ErgoBoxes, ErgoBoxCandidates,
@@ -7,31 +9,6 @@ import {
   SimpleBoxSelector, Tokens, Token, TokenAmount, TokenId, BlockHeaders, PreHeader, Transaction,
 } from '../pkg/ergo_lib_wasm';
 
-const block_headers = BlockHeaders.from_json([{
-  "extensionId": "d16f25b14457186df4c5f6355579cc769261ce1aebc8209949ca6feadbac5a3f",
-  "difficulty": "626412390187008",
-  "votes": "040000",
-  "timestamp": 1618929697400,
-  "size": 221,
-  "stateRoot": "8ad868627ea4f7de6e2a2fe3f98fafe57f914e0f2ef3331c006def36c697f92713",
-  "height": 471746,
-  "nBits": 117586360,
-  "version": 2,
-  "id": "4caa17e62fe66ba7bd69597afdc996ae35b1ff12e0ba90c22ff288a4de10e91b",
-  "adProofsRoot": "d882aaf42e0a95eb95fcce5c3705adf758e591532f733efe790ac3c404730c39",
-  "transactionsRoot": "63eaa9aff76a1de3d71c81e4b2d92e8d97ae572a8e9ab9e66599ed0912dd2f8b",
-  "extensionHash": "3f91f3c680beb26615fdec251aee3f81aaf5a02740806c167c0f3c929471df44",
-  "powSolutions": {
-    "pk": "02b3a06d6eaa8671431ba1db4dd427a77f75a5c2acbd71bfb725d38adc2b55f669",
-    "w": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
-    "n": "5939ecfee6b0d7f4",
-    "d": 0
-  },
-  "adProofsId": "86eaa41f328bee598e33e52c9e515952ad3b7874102f762847f17318a776a7ae",
-  "transactionsId": "ac80245714f25aa2fafe5494ad02a26d46e7955b8f5709f3659f1b9440797b3e",
-  "parentId": "6481752bace5fa5acba5d5ef7124d48826664742d46c974c98a2d60ace229a34"
-}]);
-
 it('TxBuilder test', async () => {
   const recipient = Address.from_testnet_str('3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN');
   const unspent_boxes = ErgoBoxes.from_boxes_json([
@@ -86,8 +63,9 @@ it('sign transaction', async () => {
   // smoke test for to_js_eip12
   assert(tx.to_js_eip12() != null);
   const tx_data_inputs = ErgoBoxes.from_boxes_json([]);
+  const block_headers = generate_block_headers();
   const pre_header = PreHeader.from_block_header(block_headers.get(0));
-  const ctx = new ErgoStateContext(pre_header);
+  const ctx = new ErgoStateContext(pre_header, block_headers);
   const sks = new SecretKeys();
   sks.add(sk);
   const wallet = Wallet.from_secrets(sks);
@@ -200,8 +178,9 @@ it('use signed tx outputs as inputs in a new tx', async () => {
   const tx_builder = TxBuilder.new(box_selection, tx_outputs, 0, fee, change_address, min_change_value);
   const tx = tx_builder.build();
   const tx_data_inputs = ErgoBoxes.from_boxes_json([]);
+  const block_headers = generate_block_headers();
   const pre_header = PreHeader.from_block_header(block_headers.get(0));
-  const ctx = new ErgoStateContext(pre_header);
+  const ctx = new ErgoStateContext(pre_header, block_headers);
   const sks = new SecretKeys();
   sks.add(sk);
   const wallet = Wallet.from_secrets(sks);
diff --git a/bindings/ergo-lib-wasm/tests/utils.js b/bindings/ergo-lib-wasm/tests/utils.js
new file mode 100644
index 000000000..118f3003f
--- /dev/null
+++ b/bindings/ergo-lib-wasm/tests/utils.js
@@ -0,0 +1,41 @@
+
+import { BlockHeaders } from '../pkg/ergo_lib_wasm';
+
+export const generate_block_headers = (transformFn, count=10) => {
+  const headers = [];
+
+  for (let i = 0; i < count; i++) {
+    const header = {
+      "extensionId": "d16f25b14457186df4c5f6355579cc769261ce1aebc8209949ca6feadbac5a3f",
+      "difficulty": "626412390187008",
+      "votes": "040000",
+      "timestamp": 1618929697400,
+      "size": "221",
+      "stateRoot": "8ad868627ea4f7de6e2a2fe3f98fafe57f914e0f2ef3331c006def36c697f92713",
+      "height": 471746,
+      "nBits": 117586360,
+      "version": 2,
+      "id": "4caa17e62fe66ba7bd69597afdc996ae35b1ff12e0ba90c22ff288a4de10e91b",
+      "adProofsRoot": "d882aaf42e0a95eb95fcce5c3705adf758e591532f733efe790ac3c404730c39",
+      "transactionsRoot": "63eaa9aff76a1de3d71c81e4b2d92e8d97ae572a8e9ab9e66599ed0912dd2f8b",
+      "extensionHash": "3f91f3c680beb26615fdec251aee3f81aaf5a02740806c167c0f3c929471df44",
+      "powSolutions": {
+        "pk": "02b3a06d6eaa8671431ba1db4dd427a77f75a5c2acbd71bfb725d38adc2b55f669",
+        "w": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
+        "n": "5939ecfee6b0d7f4",
+        "d": 0
+      },
+      "adProofsId": "86eaa41f328bee598e33e52c9e515952ad3b7874102f762847f17318a776a7ae",
+      "transactionsId": "ac80245714f25aa2fafe5494ad02a26d46e7955b8f5709f3659f1b9440797b3e",
+      "parentId": "6481752bace5fa5acba5d5ef7124d48826664742d46c974c98a2d60ace229a34"
+    };
+
+    if (transformFn) {
+      transformFn(header);
+    }
+
+    headers.push(header);
+  }
+
+  return BlockHeaders.from_json(headers);
+}