diff --git a/crypto/fift/lib/Fift.fif b/crypto/fift/lib/Fift.fif index 59cbcb71..65fecb31 100644 --- a/crypto/fift/lib/Fift.fif +++ b/crypto/fift/lib/Fift.fif @@ -25,7 +25,7 @@ { bl word 1 { -rot 2 'nop does swap 0 (create) } } :: 2=: { } : s>c -{ s>c hash } : shash +{ s>c hashB } : shash // to be more efficiently re-implemented in C++ in the future { dup 0< ' negate if } : abs { 2dup > ' swap if } : minmax diff --git a/crypto/fift/words.cpp b/crypto/fift/words.cpp index 01dcf802..ea4744c8 100644 --- a/crypto/fift/words.cpp +++ b/crypto/fift/words.cpp @@ -764,13 +764,17 @@ void interpret_string_to_bytes(vm::Stack& stack) { stack.push_bytes(stack.pop_string()); } -void interpret_bytes_hash(vm::Stack& stack) { +void interpret_bytes_hash(vm::Stack& stack, bool as_uint) { std::string str = stack.pop_bytes(); unsigned char buffer[32]; digest::hash_str(buffer, str.c_str(), str.size()); - td::RefInt256 x{true}; - x.write().import_bytes(buffer, 32, false); - stack.push_int(std::move(x)); + if (as_uint) { + td::RefInt256 x{true}; + x.write().import_bytes(buffer, 32, false); + stack.push_int(std::move(x)); + } else { + stack.push_bytes(std::string{(char*)buffer, 32}); + } } void interpret_empty(vm::Stack& stack) { @@ -892,11 +896,15 @@ void interpret_builder_remaining_bitrefs(vm::Stack& stack, int mode) { } } -void interpret_cell_hash(vm::Stack& stack) { +void interpret_cell_hash(vm::Stack& stack, bool as_uint) { auto cell = stack.pop_cell(); - td::RefInt256 hash{true}; - hash.write().import_bytes(cell->get_hash().as_slice().ubegin(), 32, false); - stack.push_int(std::move(hash)); + if (as_uint) { + td::RefInt256 hash{true}; + hash.write().import_bytes(cell->get_hash().as_slice().ubegin(), 32, false); + stack.push_int(std::move(hash)); + } else { + stack.push_bytes(cell->get_hash().as_slice().str()); + } } void interpret_store_ref(vm::Stack& stack) { @@ -959,7 +967,9 @@ void interpret_fetch_bytes(vm::Stack& stack, int mode) { unsigned n = stack.pop_smallint_range(127); auto cs = stack.pop_cellslice(); if (!cs->have(n * 8)) { - stack.push(std::move(cs)); + if (mode & 2) { + stack.push(std::move(cs)); + } stack.push_bool(false); if (!(mode & 4)) { throw IntError{"end of data while reading byte string from cell"}; @@ -970,7 +980,7 @@ void interpret_fetch_bytes(vm::Stack& stack, int mode) { if (mode & 2) { cs.write().fetch_bytes(tmp, n); } else { - cs.write().prefetch_bytes(tmp, n); + cs->prefetch_bytes(tmp, n); } std::string s{tmp, tmp + n}; if (mode & 1) { @@ -978,7 +988,9 @@ void interpret_fetch_bytes(vm::Stack& stack, int mode) { } else { stack.push_string(std::move(s)); } - stack.push(std::move(cs)); + if (mode & 2) { + stack.push(std::move(cs)); + } if (mode & 4) { stack.push_bool(true); } @@ -1009,13 +1021,15 @@ void interpret_cell_remaining(vm::Stack& stack) { void interpret_fetch_ref(vm::Stack& stack, int mode) { auto cs = stack.pop_cellslice(); if (!cs->have_refs(1)) { - stack.push(std::move(cs)); + if (mode & 2) { + stack.push(std::move(cs)); + } stack.push_bool(false); if (!(mode & 4)) { throw IntError{"end of data while reading reference from cell"}; } } else { - auto cell = (mode & 2) ? cs.write().fetch_ref() : cs.write().prefetch_ref(); + auto cell = (mode & 2) ? cs.write().fetch_ref() : cs->prefetch_ref(); if (mode & 2) { stack.push(std::move(cs)); } @@ -2474,7 +2488,9 @@ void init_words_common(Dictionary& d) { d.def_stack_word("B>Lu@+ ", std::bind(interpret_bytes_fetch_int, _1, 0x12)); d.def_stack_word("B>Li@+ ", std::bind(interpret_bytes_fetch_int, _1, 0x13)); d.def_stack_word("$>B ", interpret_string_to_bytes); - d.def_stack_word("Bhash ", interpret_bytes_hash); + d.def_stack_word("Bhash ", std::bind(interpret_bytes_hash, _1, true)); + d.def_stack_word("Bhashu ", std::bind(interpret_bytes_hash, _1, true)); + d.def_stack_word("BhashB ", std::bind(interpret_bytes_hash, _1, false)); // cell manipulation (create, write and modify cells) d.def_stack_word("B dup Bx. cr dup "basestate0" +suffix +".boc" tuck B>file ."(Initial basechain state saved to file " type .")" cr -Bhash dup =: basestate0_fhash +Bhashu dup =: basestate0_fhash ."file hash=" dup x. space 256 u>B dup B>base64url type cr "basestate0" +suffix +".fhash" B>file -hash dup =: basestate0_rhash +hashu dup =: basestate0_rhash ."root hash=" dup x. space 256 u>B dup B>base64url type cr "basestate0" +suffix +".rhash" B>file @@ -227,10 +227,10 @@ cr cr ."new state is:" cr dup B dup Bx. cr dup "zerostate" +suffix +".boc" tuck B>file ."(Initial masterchain state saved to file " type .")" cr -Bhash dup =: zerostate_fhash +Bhashu dup =: zerostate_fhash ."file hash=" dup x. space 256 u>B dup B>base64url type cr "zerostate" +suffix +".fhash" B>file -hash dup =: zerostate_rhash ."root hash=" dup x. space 256 u>B dup B>base64url type cr +hashu dup =: zerostate_rhash ."root hash=" dup x. space 256 u>B dup B>base64url type cr "zerostate" +suffix +".rhash" B>file basestate0_rhash ."Basestate0 root hash=" dup x. space 256 u>B B>base64url type cr basestate0_fhash ."Basestate0 file hash=" dup x. space 256 u>B B>base64url type cr diff --git a/crypto/smartcont/gen-zerostate.fif b/crypto/smartcont/gen-zerostate.fif index f5f9aac3..43ba07b1 100644 --- a/crypto/smartcont/gen-zerostate.fif +++ b/crypto/smartcont/gen-zerostate.fif @@ -15,10 +15,10 @@ cr ."initial basechain state is:" cr dup B dup Bx. cr dup "basestate0" +suffix +".boc" tuck B>file ."(Initial basechain state saved to file " type .")" cr -Bhash dup =: basestate0_fhash +Bhashu dup =: basestate0_fhash ."file hash=" dup x. space 256 u>B dup B>base64url type cr "basestate0" +suffix +".fhash" B>file -hash dup =: basestate0_rhash +hashu dup =: basestate0_rhash ."root hash=" dup x. space 256 u>B dup B>base64url type cr "basestate0" +suffix +".rhash" B>file @@ -231,10 +231,10 @@ cr cr ."new state is:" cr dup B dup Bx. cr dup "zerostate" +suffix +".boc" tuck B>file ."(Initial masterchain state saved to file " type .")" cr -Bhash dup =: zerostate_fhash +Bhashu dup =: zerostate_fhash ."file hash= " dup X. space 256 u>B dup B>base64url type cr "zerostate" +suffix +".fhash" B>file -hash dup =: zerostate_rhash ."root hash= " dup X. space 256 u>B dup B>base64url type cr +hashu dup =: zerostate_rhash ."root hash= " dup X. space 256 u>B dup B>base64url type cr "zerostate" +suffix +".rhash" B>file basestate0_rhash ."Basestate0 root hash= " dup X. space 256 u>B B>base64url type cr basestate0_fhash ."Basestate0 file hash= " dup X. space 256 u>B B>base64url type cr diff --git a/crypto/smartcont/highload-wallet.fif b/crypto/smartcont/highload-wallet.fif index 58ebda06..ab69fdf3 100644 --- a/crypto/smartcont/highload-wallet.fif +++ b/crypto/smartcont/highload-wallet.fif @@ -59,7 +59,7 @@ order-file include // create external message dup ."signing message: " dup ."resulting external message: " // data null // no libraries // create StateInit dup ."StateInit: " dup ."signing message: " dup ."External message for initialization is " B dup Bx. cr diff --git a/crypto/smartcont/new-pinger.fif b/crypto/smartcont/new-pinger.fif index b8332688..646bfdf6 100644 --- a/crypto/smartcont/new-pinger.fif +++ b/crypto/smartcont/new-pinger.fif @@ -41,7 +41,7 @@ def? $3 { @' $3 } { "new-pinger" } cond constant file-base // no libraries // create StateInit dup ."StateInit: " // create StateInit dup ."StateInit: " // data null // no libraries // create StateInit dup ."StateInit: " dup ."signing message: " dup ."External message for initialization is " B dup Bx. cr diff --git a/crypto/smartcont/new-wallet.fif b/crypto/smartcont/new-wallet.fif index c1ad403c..9bdb0bdb 100644 --- a/crypto/smartcont/new-wallet.fif +++ b/crypto/smartcont/new-wallet.fif @@ -47,14 +47,14 @@ null // no libraries // Libs{ x{ABACABADABACABA} drop x{AAAA} s>c public_lib x{1234} x{5678} |_ s>c public_lib }Libs // create StateInit dup ."StateInit: " dup ."signing message: " dup ."External message for initialization is " B dup Bx. cr diff --git a/crypto/smartcont/update-config-smc.fif b/crypto/smartcont/update-config-smc.fif index 3840e128..22f01f04 100644 --- a/crypto/smartcont/update-config-smc.fif +++ b/crypto/smartcont/update-config-smc.fif @@ -6,7 +6,7 @@ ."with private key loaded from file .pk, " ."and saves it into .boc ('config-query.boc' by default)" cr 1 halt } : usage -def? $# { @' $# dup 2 < swap 3 > or ' usage if } if +$# dup 2 < swap 3 > or ' usage if "config-master" constant file-base 0 constant seqno @@ -15,18 +15,16 @@ true constant bounce "config-code.fif" constant config-source 100 constant interval // valid for 100 seconds -def? $2 { - @' $1 =: file-base - @' $2 parse-int =: seqno -} if -def? $5 { @' $5 } { "config-query" } cond constant savefile +$1 =: file-base +$2 parse-int =: seqno +def? $3 { @' $3 } { "config-query" } cond constant savefile file-base +".addr" load-address 2dup 2constant config_addr ."Configuration smart contract address = " 2dup .addr cr 6 .Addr cr file-base +".pk" load-keypair nip constant config_pk -."Loading new configuration smart contract code from file file " config-source type cr +."Loading new configuration smart contract code from file " config-source type cr "Asm.fif" include config-source include dup dup ."signing message: " dup ."resulting external message: " .pk, " ."and saves it into .boc ('config-query.boc' by default)" cr 1 halt } : usage -def? $# { @' $# dup 4 < swap 5 > or ' usage if } if +$# dup 4 < swap 5 > or ' usage if "config-master" constant file-base 0 constant seqno @@ -15,12 +15,10 @@ true constant bounce "new-value.boc" constant boc-filename 100 constant interval // valid for 100 seconds -def? $4 { - @' $1 =: file-base - @' $2 parse-int =: seqno - @' $3 parse-int =: idx - @' $4 =: boc-filename -} if +$1 =: file-base +$2 parse-int =: seqno +$3 parse-int =: idx +$4 =: boc-filename def? $5 { @' $5 } { "config-query" } cond constant savefile file-base +".addr" load-address @@ -39,7 +37,7 @@ dup idx is-valid-config? not abort"not a valid value for chosen configuration pa // create a message dup ."signing message: " dup ."resulting external message: " .pk, " ."and saves it into .boc ('config-query.boc' by default)" cr 1 halt } : usage -def? $# { @' $# dup 2 < swap 3 > or ' usage if } if +$# dup 2 < swap 3 > or ' usage if "config-master" constant file-base 0 constant seqno @@ -15,18 +15,16 @@ true constant bounce "elector-code.fif" constant elector-source 100 constant interval // valid for 100 seconds -def? $2 { - @' $1 =: file-base - @' $2 parse-int =: seqno -} if -def? $5 { @' $5 } { "config-query" } cond constant savefile +$1 =: file-base +$2 parse-int =: seqno +def? $3 { @' $3 } { "config-query" } cond constant savefile file-base +".addr" load-address 2dup 2constant config_addr ."Configuration smart contract address = " 2dup .addr cr 6 .Addr cr file-base +".pk" load-keypair nip constant config_pk -."Loading new elector smart contract code from file file " elector-source type cr +."Loading new elector smart contract code from file " elector-source type cr "Asm.fif" include elector-source include dup dup ."signing message: " dup ."resulting external message: " dup ."resulting external message: " dup ."resulting external message: " root) usage_root = UsageCell::create(orig_root, usage_tree->root_ptr()); } -void MerkleProofBuilder::reset(Ref root) { +Ref MerkleProofBuilder::init(Ref root) { usage_tree = std::make_shared(); orig_root = std::move(root); usage_root = UsageCell::create(orig_root, usage_tree->root_ptr()); + return usage_root; } -void MerkleProofBuilder::clear() { +bool MerkleProofBuilder::clear() { usage_tree.reset(); orig_root.clear(); usage_root.clear(); + return true; } Ref MerkleProofBuilder::extract_proof() const { diff --git a/crypto/vm/cells/MerkleProof.h b/crypto/vm/cells/MerkleProof.h index 8dfb43a6..5b3b8ebc 100644 --- a/crypto/vm/cells/MerkleProof.h +++ b/crypto/vm/cells/MerkleProof.h @@ -51,9 +51,10 @@ class MerkleProofBuilder { Ref orig_root, usage_root; public: + MerkleProofBuilder() = default; MerkleProofBuilder(Ref root); - void reset(Ref root); - void clear(); + Ref init(Ref root); + bool clear(); Ref root() const { return usage_root; } diff --git a/doc/FullNode-HOWTO b/doc/FullNode-HOWTO index 843c6803..75432ffb 100644 --- a/doc/FullNode-HOWTO +++ b/doc/FullNode-HOWTO @@ -107,7 +107,7 @@ Replace it with the following: } ], -`control.0.id` is set to the base64 identifier of the server's public key, and `control.0.allowed.0.id` is the base64 identifier of the client's public key. is the UDP port the server will listen to for console commands. +`control.0.id` is set to the base64 identifier of the server's public key, and `control.0.allowed.0.id` is the base64 identifier of the client's public key. is the TCP port the server will listen to for console commands. 7. Running the Full Node ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/fiftbase.tex b/doc/fiftbase.tex index 35b21d9f..3347dd86 100644 --- a/doc/fiftbase.tex +++ b/doc/fiftbase.tex @@ -1233,8 +1233,9 @@ first outputs ``{\tt x\{F55AA\}}'', and then throws an exception with the messag \mysubsection{Cell hash operations}\label{p:hash.ops} There are few words that operate on {\em Cell\/}s directly. The most important of them computes the {\em ($\Sha$-based) representation hash\/} of a given cell (cf.~\cite[3.1]{TVM}), which can be roughly described as the $\Sha$ hash of the cell's data bits concatenated with recursively computed hashes of the cells referred to by this cell: \begin{itemize} -\item {\tt hash} ($c$ -- $B$), computes the $\Sha$-based representation hash of {\em Cell\/}~$c$ (cf.~\cite[3.1]{TVM}), which unambiguously defines $c$ and all its descendants (provided there are no collisions for $\Sha$). The result is returned as a {\em Bytes\/} value consisting of exactly 32 bytes. -\item {\tt shash} ($s$ -- $B$), computes the $\Sha$-based representation hash of a {\em Slice\/} by first transforming it into a cell. Equivalent to {\tt s>c hash}. +\item {\tt hashB} ($c$ -- $B$), computes the $\Sha$-based representation hash of {\em Cell\/}~$c$ (cf.~\cite[3.1]{TVM}), which unambiguously defines $c$ and all its descendants (provided there are no collisions for $\Sha$). The result is returned as a {\em Bytes\/} value consisting of exactly 32 bytes. +\item {\tt hashu} ($c$ -- $x$), computes the $\Sha$-based representation hash of $c$ as above, but returns the result as a big-endian unsigned 256-bit {\em Integer}. +\item {\tt shash} ($s$ -- $B$), computes the $\Sha$-based representation hash of a {\em Slice\/} by first transforming it into a cell. Equivalent to {\tt s>c hashB}. \end{itemize} \mysubsection{Bag-of-cells operations}\label{p:boc.ops} @@ -1286,7 +1287,8 @@ prints ``{\tt 17239 -1000000001 ok}''. Additionally, there are several words for directly packing (serializing) data into {\em Bytes\/} values, and unpacking (deserializing) them afterwards. They can be combined with {\tt B>file} and {\tt file>B} to save data directly into binary files, and load them afterwards. \begin{itemize} \item {\tt Blen} ($B$ -- $x$), returns the length of a {\em Bytes\/} value~$B$ in bytes. -\item {\tt Bhash} ($B$ -- $B'$), computes the $\Sha$ hash of a {\em Bytes\/} value. The hash is returned as a 32-byte {\em Bytes\/} value. +\item {\tt BhashB} ($B$ -- $B'$), computes the $\Sha$ hash of a {\em Bytes\/} value. The hash is returned as a 32-byte {\em Bytes\/} value. +\item {\tt Bhashu} ($B$ -- $x$), computes the $\Sha$ hash of a {\em Bytes\/} value and returns the hash as an unsigned 256-bit big-endian integer. \item {\tt B=} ($B$ $B'$ -- $?$), checks whether two {\em Bytes\/} sequences are equal. \item {\tt Bcmp} ($B$ $B'$ -- $x$), lexicographically compares two {\em Bytes\/} sequences, and returns $-1$, $0$, or $1$, depending on the comparison result. \item {\tt B>i@} ($B$ $x$ -- $y$), deserializes the first $x/8$ bytes of a {\em Bytes} value~$B$ as a signed big-endian $x$-bit {\em Integer}~$y$. @@ -1970,7 +1972,9 @@ For example, the active prefix word {\tt B\{}, used for defining {\em Bytes\/} l \item {\tt B@?} ($s$ $x$ -- $B$ $-1$ or $0$), similar to {\tt B@}, but uses a flag to indicate failure instead of throwing an exception, cf.~\ptref{p:slice.ops}. \item {\tt B@?+} ($s$ $x$ -- $B$ $s'$ $-1$ or $s$ $0$), similar to {\tt B@+}, but uses a flag to indicate failure instead of throwing an exception, cf.~\ptref{p:slice.ops}. \item {\tt Bcmp} ($B$ $B'$ -- $x$), lexicographically compares two {\em Bytes\/} sequences, and returns $-1$, $0$, or $1$, depending on the comparison result, cf.~\ptref{p:bytes.ops}. -\item {\tt Bhash} ($B$ -- $B'$), computes the $\Sha$ hash of a {\em Bytes\/} value, cf.~\ptref{p:bytes.ops}. The hash is returned as a 32-byte {\em Bytes\/} value. +\item {\tt Bhash} ($B$ -- $x$), deprecated version of {\tt Bhashu}. Use {\tt Bhashu} or {\tt BhashB} instead. +\item {\tt BhashB} ($B$ -- $B'$), computes the $\Sha$ hash of a {\em Bytes\/} value, cf.~\ptref{p:bytes.ops}. The hash is returned as a 32-byte {\em Bytes\/} value. +\item {\tt Bhashu} ($B$ -- $x$), computes the $\Sha$ hash of a {\em Bytes\/} value, cf.~\ptref{p:bytes.ops}. The hash is returned as a big-endian unsigned 256-bit {\em Integer\/} value. \item {\tt Blen} ($B$ -- $x$), returns the length of a {\em Bytes\/} value~$B$ in bytes, cf.~\ptref{p:bytes.ops}. \item {\tt Bx.} ($B$ -- ), prints the hexadecimal representation of a {\em Bytes\/} value, cf.~\ptref{p:bytes.ops}. Each byte is represented by exactly two uppercase hexadecimal digits. \item {\tt \underline{B\{}$\langle{\textit{hex-digits}}\rangle$\}} ( -- $B$), pushes a {\em Bytes\/} literal containing data represented by an even number of hexadecimal digits, cf.~\ptref{p:bytes.ops}. @@ -2062,7 +2066,9 @@ Typical values of $x$ are $x=0$ or $x=2$ for very small bags of cells (e.g., TON \item {\tt gasrunvmcode} (\dots $s$ $z$ -- \dots $x$ $z'$), a gas-aware version of {\tt runvmcode}, cf.~\ptref{p:tvm.ops}: invokes a new instance of TVM with the current continuation {\tt cc} initialized from {\em Slice\/} $s$ and with the gas limit set to $z$, thus executing code~$s$ in TVM. The original Fift stack (without $s$) is passed in its entirety as the initial stack of the new TVM instance. When TVM terminates, its resulting stack is used as the new Fift stack, with the exit code $x$ and the actually consumed gas $z'$ pushed at its top. If $x$ is non-zero, indicating that TVM has been terminated by an unhandled exception, the next stack entry from the top contains the parameter of this exception, and $x$ is the exception code. All other entries are removed from the stack in this case. \item {\tt gasrunvmdict} (\dots $s$ $z$ -- \dots $x$ $z'$), a gas-aware version of {\tt runvmdict}, cf.~\ptref{p:tvm.ops}: invokes a new instance of TVM with the current continuation {\tt cc} initialized from {\em Slice\/} $s$ and sets the gas limit to $z$ similarly to {\tt gasrunvmcode}, but also initializes the special register {\tt c3} with the same value, and pushes a zero into the initial TVM stack before the TVM execution begins. The actually consumed gas is returned as an {\em Integer\/} $z'$. In a typical application {\em Slice\/}~$s$ consists of a subroutine selection code that uses the top-of-stack {\em Integer\/} to select the subroutine to be executed, thus enabling the definition and execution of several mutually-recursive subroutines (cf.~\cite[4.6]{TVM} and~\ptref{p:asm.prog}). The selector equal to zero corresponds to the {\tt main()} subroutine in a large TVM program. \item {\tt halt} ($x$ -- ), quits to the operating system similarly to {\tt bye}, but uses {\em Integer\/} $x$ as the exit code, cf.~\ptref{p:exit.fift}. -\item {\tt hash} ($c$ -- $B$), computes the $\Sha$-based representation hash of {\em Cell\/}~$c$ (cf.~\cite[3.1]{TVM}), which unambiguously defines $c$ and all its descendants (provided there are no collisions for $\Sha$), cf.~\ptref{p:hash.ops}. The result is returned as a {\em Bytes\/} value consisting of exactly 32 bytes. +\item {\tt hash} ($c$ -- $x$), a deprecated version of {\tt hashu}. Use {\tt hashu} or {\tt hashB} instead. +\item {\tt hashB} ($c$ -- $B$), computes the $\Sha$-based representation hash of {\em Cell\/}~$c$ (cf.~\cite[3.1]{TVM}), which unambiguously defines $c$ and all its descendants (provided there are no collisions for $\Sha$), cf.~\ptref{p:hash.ops}. The result is returned as a {\em Bytes\/} value consisting of exactly 32 bytes. +\item {\tt hashu} ($c$ -- $x$), computes the $\Sha$-based representation hash of {\em Cell\/}~$c$ similarly to {\tt hashB}, but returns the result as a big-endian unsigned 256-bit {\em Integer}. \item {\tt hold} ($S$ $x$ -- $S'$), appends to {\em String\/}~$S$ one UTF-8 encoded character with Unicode codepoint~$x$. Equivalent to {\tt chr \$+}. \item {\tt hole} ( -- $p$), creates a new {\em Box\/}~$p$ that does not hold any value, cf.~\ptref{p:variables}. Equivalent to {\tt null box}. \item {\tt i,} ($b$ $x$ $y$ -- $b'$), appends the big-endian binary representation of a signed $y$-bit integer~$x$ to {\em Builder\/}~$b$, where $0\leq y\leq 257$, cf.~\ptref{p:builder.ops}. If there is not enough room in $b$ (i.e., if $b$ already contains more than $1023-y$ data bits), or if {\em Integer\/}~$x$ does not fit into $y$ bits, an exception is thrown. @@ -2120,7 +2126,7 @@ Typical values of $x$ are $x=0$ or $x=2$ for very small bags of cells (e.g., TON \item {\tt sbits} ($s$ -- $x$), returns the number of data bits $x$ remaining in {\em Slice}~$s$, cf.~\ptref{p:slice.ops}. \item {\tt second} ($t$ -- $x$), returns the second component of a {\em Tuple}, cf.~\ptref{p:tuples}. Equivalent to {\tt 1 []}. \item {\tt sgn} ($x$ -- $y$), computes the sign of an {\em Integer\/} $x$ (i.e., pushes $1$ if $x>0$, $-1$ if $x<0$, and $0$ if $x=0$), cf.~\ptref{p:int.comp}. Equivalent to {\tt 0 cmp}. -\item {\tt shash} ($s$ -- $B$), computes the $\Sha$-based representation hash of a {\em Slice\/} by first transforming it into a cell, cf.~\ptref{p:hash.ops}. Equivalent to {\tt s>c hash}. +\item {\tt shash} ($s$ -- $B$), computes the $\Sha$-based representation hash of a {\em Slice\/} by first transforming it into a cell, cf.~\ptref{p:hash.ops}. Equivalent to {\tt s>c hashB}. \item {\tt sign} ($S$ $x$ -- $S'$), appends a minus sign ``{\tt -}'' to {\em String\/}~$S$ if {\em Integer\/}~$x$ is negative. Otherwise leaves $S$ intact. \item {\tt single} ($x$ -- $t$), creates new singleton $t=(x)$, i.e., a one-element {\em Tuple}. Equivalent to {\tt 1 tuple}. \item {\tt skipspc} ( -- ), skips blank characters from the current input line until a non-blank or an end-of-line character is found. diff --git a/lite-client/lite-client.cpp b/lite-client/lite-client.cpp index 0d446e6e..3817e987 100644 --- a/lite-client/lite-client.cpp +++ b/lite-client/lite-client.cpp @@ -914,8 +914,10 @@ bool TestNode::do_parse_line() { return parse_block_id_ext(blkid) && parse_account_addr(workchain, addr) && parse_lt(lt) && seekeoln() && get_one_transaction(blkid, workchain, addr, lt, true); } else if (word == "lasttrans" || word == "lasttransdump") { - return parse_account_addr(workchain, addr) && parse_lt(lt) && parse_hash(hash) && seekeoln() && - get_last_transactions(workchain, addr, lt, hash, 10, word == "lasttransdump"); + count = 10; + return parse_account_addr(workchain, addr) && parse_lt(lt) && parse_hash(hash) && + (seekeoln() || parse_uint32(count)) && seekeoln() && + get_last_transactions(workchain, addr, lt, hash, count, word == "lasttransdump"); } else if (word == "listblocktrans" || word == "listblocktransrev") { lt = 0; int mode = (word == "listblocktrans" ? 7 : 0x47); diff --git a/validator/db/filedb.cpp b/validator/db/filedb.cpp index 6e209987..eea33b5c 100644 --- a/validator/db/filedb.cpp +++ b/validator/db/filedb.cpp @@ -353,6 +353,45 @@ FileDb::DbEntry::DbEntry(tl_object_ptr entry) , file_hash(entry->file_hash_) { } +void FileDb::prepare_stats(td::Promise>> promise) { + std::vector> rocksdb_stats; + auto stats = kv_->stats(); + if (stats.size() == 0) { + promise.set_value(std::move(rocksdb_stats)); + return; + } + size_t pos = 0; + while (pos < stats.size()) { + while (pos < stats.size() && + (stats[pos] == ' ' || stats[pos] == '\n' || stats[pos] == '\r' || stats[pos] == '\t')) { + pos++; + } + auto p = pos; + if (pos == stats.size()) { + break; + } + while (stats[pos] != '\n' && stats[pos] != '\r' && stats[pos] != ' ' && stats[pos] != '\t' && pos < stats.size()) { + pos++; + } + auto name = stats.substr(p, pos - p); + if (stats[pos] == '\n' || pos == stats.size()) { + rocksdb_stats.emplace_back(name, ""); + continue; + } + while (pos < stats.size() && + (stats[pos] == ' ' || stats[pos] == '\n' || stats[pos] == '\r' || stats[pos] == '\t')) { + pos++; + } + p = pos; + while (stats[pos] != '\n' && stats[pos] != '\r' && pos < stats.size()) { + pos++; + } + auto value = stats.substr(p, pos - p); + rocksdb_stats.emplace_back(name, value); + } + promise.set_value(std::move(rocksdb_stats)); +} + } // namespace validator } // namespace ton diff --git a/validator/db/filedb.hpp b/validator/db/filedb.hpp index ad8c03b5..5bb1ea8f 100644 --- a/validator/db/filedb.hpp +++ b/validator/db/filedb.hpp @@ -147,6 +147,8 @@ class FileDb : public td::actor::Actor { void load_file_slice(RefId ref_id, td::int64 offset, td::int64 max_size, td::Promise promise); void check_file(RefId ref_id, td::Promise promise); + void prepare_stats(td::Promise>> promise); + void start_up() override; void alarm() override; diff --git a/validator/db/rootdb.cpp b/validator/db/rootdb.cpp index 9b487d7f..fb59dfc4 100644 --- a/validator/db/rootdb.cpp +++ b/validator/db/rootdb.cpp @@ -24,6 +24,7 @@ #include "ton/ton-tl.hpp" #include "td/utils/overloaded.h" #include "common/checksum.h" +#include "validator/stats-merger.h" namespace ton { @@ -473,6 +474,13 @@ void RootDb::allow_gc(FileDb::RefId ref_id, bool is_archive, td::Promise p })); } +void RootDb::prepare_stats(td::Promise>> promise) { + auto merger = StatsMerger::create(std::move(promise)); + + td::actor::send_closure(file_db_, &FileDb::prepare_stats, merger.make_promise("filedb.")); + td::actor::send_closure(archive_db_, &FileDb::prepare_stats, merger.make_promise("archivedb.")); +} + } // namespace validator } // namespace ton diff --git a/validator/db/rootdb.hpp b/validator/db/rootdb.hpp index fc285fdd..d03b590d 100644 --- a/validator/db/rootdb.hpp +++ b/validator/db/rootdb.hpp @@ -110,6 +110,8 @@ class RootDb : public Db { void allow_block_gc(BlockIdExt block_id, td::Promise promise); void allow_gc(FileDb::RefId ref_id, bool is_archive, td::Promise promise); + void prepare_stats(td::Promise>> promise) override; + private: td::actor::ActorId validator_manager_; diff --git a/validator/impl/liteserver.cpp b/validator/impl/liteserver.cpp index b59a64fe..2a01c99c 100644 --- a/validator/impl/liteserver.cpp +++ b/validator/impl/liteserver.cpp @@ -780,11 +780,10 @@ bool LiteQuery::make_state_root_proof(Ref& proof, Ref state_ CHECK(block_root.not_null() && state_root.not_null()); RootHash rhash{block_root->get_hash().bits()}; CHECK(rhash == blkid.root_hash); - auto usage_tree = std::make_shared(); - auto usage_cell = vm::UsageCell::create(block_root, usage_tree->root_ptr()); + vm::MerkleProofBuilder pb{std::move(block_root)}; block::gen::Block::Record blk; block::gen::BlockInfo::Record info; - if (!(tlb::unpack_cell(usage_cell, blk) && tlb::unpack_cell(blk.info, info))) { + if (!(tlb::unpack_cell(pb.root(), blk) && tlb::unpack_cell(blk.info, info))) { return fatal_error("cannot unpack block header"); } vm::CellSlice upd_cs{vm::NoVmSpec(), blk.state_update}; @@ -797,8 +796,7 @@ bool LiteQuery::make_state_root_proof(Ref& proof, Ref state_ if (upd_hash.compare(state_hash, 256)) { return fatal_error("cannot construct Merkle proof for given masterchain state because of hash mismatch"); } - proof = vm::MerkleProof::generate(block_root, usage_tree.get()); - if (proof.is_null()) { + if (!pb.extract_proof_to(proof)) { return fatal_error("unknown error creating Merkle proof"); } return true; @@ -806,20 +804,17 @@ bool LiteQuery::make_state_root_proof(Ref& proof, Ref state_ bool LiteQuery::make_shard_info_proof(Ref& proof, vm::CellSlice& cs, ShardIdFull shard, ShardIdFull& true_shard, Ref& leaf, bool& found, bool exact) { - auto state_root = mc_state_->root_cell(); - auto usage_tree = std::make_shared(); - auto usage_cell = vm::UsageCell::create(state_root, usage_tree->root_ptr()); + vm::MerkleProofBuilder pb{mc_state_->root_cell()}; block::gen::ShardStateUnsplit::Record sstate; - if (!(tlb::unpack_cell(usage_cell, sstate))) { + if (!(tlb::unpack_cell(pb.root(), sstate))) { return fatal_error("cannot unpack state header"); } - auto shards_dict = block::ShardConfig::extract_shard_hashes_dict(usage_cell); + auto shards_dict = block::ShardConfig::extract_shard_hashes_dict(pb.root()); if (!shards_dict) { return fatal_error("cannot extract ShardHashes from last mc state"); } found = block::ShardConfig::get_shard_hash_raw_from(*shards_dict, cs, shard, true_shard, exact, &leaf); - proof = vm::MerkleProof::generate(state_root, usage_tree.get()); - if (proof.is_null()) { + if (!pb.extract_proof_to(proof)) { return fatal_error("unknown error creating Merkle proof"); } return true; @@ -921,11 +916,9 @@ void LiteQuery::finish_getAccountState(td::BufferSlice shard_proof) { if (!make_state_root_proof(proof1)) { return; } - auto state_root = state_->root_cell(); - auto usage_tree = std::make_shared(); - auto usage_cell = vm::UsageCell::create(state_root, usage_tree->root_ptr()); + vm::MerkleProofBuilder pb{state_->root_cell()}; block::gen::ShardStateUnsplit::Record sstate; - if (!(tlb::unpack_cell(usage_cell, sstate))) { + if (!tlb::unpack_cell(pb.root(), sstate)) { fatal_error("cannot unpack state header"); return; } @@ -935,10 +928,7 @@ void LiteQuery::finish_getAccountState(td::BufferSlice shard_proof) { if (acc_csr.not_null()) { acc_root = acc_csr->prefetch_ref(); } - auto proof2 = vm::MerkleProof::generate(state_root, usage_tree.get()); - usage_tree.reset(); - usage_cell.clear(); - auto proof = vm::std_boc_serialize_multi({std::move(proof1), std::move(proof2)}); + auto proof = vm::std_boc_serialize_multi({std::move(proof1), pb.extract_proof()}); if (proof.is_error()) { fatal_error(proof.move_as_error()); return; @@ -962,23 +952,18 @@ void LiteQuery::finish_getAccountState(td::BufferSlice shard_proof) { void LiteQuery::continue_getOneTransaction() { LOG(INFO) << "completing getOneTransaction() query"; CHECK(block_.not_null()); - auto block_root = block_->root_cell(); - auto usage_tree = std::make_shared(); - auto usage_cell = vm::UsageCell::create(block_root, usage_tree->root_ptr()); - auto trans_res = block::get_block_transaction(block_root, acc_workchain_, acc_addr_, trans_lt_); + vm::MerkleProofBuilder pb{block_->root_cell()}; + auto trans_res = block::get_block_transaction(pb.root(), acc_workchain_, acc_addr_, trans_lt_); if (trans_res.is_error()) { fatal_error(trans_res.move_as_error()); return; } auto trans_root = trans_res.move_as_ok(); - auto proof = vm::MerkleProof::generate(block_root, usage_tree.get()); - auto proof_boc = vm::std_boc_serialize(std::move(proof)); + auto proof_boc = pb.extract_proof_boc(); if (proof_boc.is_error()) { fatal_error(proof_boc.move_as_error()); return; } - usage_tree.reset(); - usage_cell.clear(); td::BufferSlice data; if (trans_root.not_null()) { auto res = vm::std_boc_serialize(std::move(trans_root)); @@ -999,10 +984,13 @@ void LiteQuery::perform_getTransactions(WorkchainId workchain, StdSmcAddress add unsigned count) { LOG(INFO) << "started a getTransactions(" << workchain << ", " << addr.to_hex() << ", " << lt << ", " << hash.to_hex() << ", " << count << ") liteserver query"; - if (count > 10) { + count = std::min(count, (unsigned)max_transaction_count); + /* + if (count > max_transaction_count) { fatal_error("cannot fetch more than 10 preceding transactions at one time"); return; } + */ if (workchain == ton::workchainInvalid) { fatal_error("invalid workchain specified"); return; @@ -1355,14 +1343,11 @@ void LiteQuery::finish_listBlockTransactions(int mode, int req_count) { CHECK(block_root.not_null()); RootHash rhash{block_root->get_hash().bits()}; CHECK(rhash == base_blk_id_.root_hash); - Ref usage_cell; - std::shared_ptr usage_tree; + vm::MerkleProofBuilder pb; + auto virt_root = block_root; if (mode & 32) { // proof requested - usage_tree = std::make_shared(); - usage_cell = vm::UsageCell::create(block_root, usage_tree->root_ptr()); - } else { - usage_cell = block_root; + virt_root = pb.init(std::move(virt_root)); } if ((mode & 192) == 64) { // reverse order, no starting point acc_addr_.set_ones(); @@ -1374,7 +1359,7 @@ void LiteQuery::finish_listBlockTransactions(int mode, int req_count) { try { block::gen::Block::Record blk; block::gen::BlockExtra::Record extra; - if (!(tlb::unpack_cell(usage_cell, blk) && tlb::unpack_cell(std::move(blk.extra), extra))) { + if (!(tlb::unpack_cell(virt_root, blk) && tlb::unpack_cell(std::move(blk.extra), extra))) { fatal_error("cannot find account transaction data in block "s + base_blk_id_.to_str()); return; } @@ -1433,8 +1418,7 @@ void LiteQuery::finish_listBlockTransactions(int mode, int req_count) { td::BufferSlice proof_data; if (mode & 32) { // create proof - auto proof = vm::MerkleProof::generate(block_root, usage_tree.get()); - auto proof_boc = vm::std_boc_serialize(std::move(proof)); + auto proof_boc = pb.extract_proof_boc(); if (proof_boc.is_error()) { fatal_error(proof_boc.move_as_error()); return; diff --git a/validator/impl/liteserver.hpp b/validator/impl/liteserver.hpp index 41077b2d..580e086b 100644 --- a/validator/impl/liteserver.hpp +++ b/validator/impl/liteserver.hpp @@ -57,7 +57,10 @@ class LiteQuery : public td::actor::Actor { std::unique_ptr chain_; public: - enum { default_timeout_msec = 4500 }; // 4.5 seconds + enum { + default_timeout_msec = 4500, // 4.5 seconds + max_transaction_count = 16 // fetch at most 16 transactions in one query + }; enum { ls_version = 0x101, ls_capabilities = 3 diff --git a/validator/interfaces/db.h b/validator/interfaces/db.h index c9ec94f3..92b28e12 100644 --- a/validator/interfaces/db.h +++ b/validator/interfaces/db.h @@ -93,6 +93,8 @@ class Db : public td::actor::Actor { virtual void get_async_serializer_state(td::Promise promise) = 0; virtual void archive(BlockIdExt block_id, td::Promise promise) = 0; + + virtual void prepare_stats(td::Promise>> promise) = 0; }; } // namespace validator diff --git a/validator/manager.cpp b/validator/manager.cpp index 8a0c79f6..142107a4 100644 --- a/validator/manager.cpp +++ b/validator/manager.cpp @@ -38,6 +38,8 @@ #include "common/delay.h" +#include "validator/stats-merger.h" + namespace ton { namespace validator { @@ -1811,6 +1813,7 @@ void ValidatorManagerImpl::advance_gc(BlockHandle handle, td::Ref>> promise) { + auto merger = StatsMerger::create(std::move(promise)); + + td::actor::send_closure(db_, &Db::prepare_stats, merger.make_promise("db.")); + std::vector> vec; vec.emplace_back("unixtime", td::to_string(static_cast(td::Clocks::system()))); - if (!last_masterchain_block_handle_) { - promise.set_value(std::move(vec)); - return; + if (last_masterchain_block_handle_) { + vec.emplace_back("masterchainblock", last_masterchain_block_id_.to_str()); + vec.emplace_back("masterchainblocktime", td::to_string(last_masterchain_block_handle_->unix_time())); + vec.emplace_back("gcmasterchainblock", gc_masterchain_handle_->id().to_str()); + vec.emplace_back("keymasterchainblock", last_key_block_handle_->id().to_str()); + vec.emplace_back("knownkeymasterchainblock", last_known_key_block_handle_->id().to_str()); + vec.emplace_back("rotatemasterchainblock", last_rotate_block_id_.to_str()); + //vec.emplace_back("shardclientmasterchainseqno", td::to_string(min_confirmed_masterchain_seqno_)); + vec.emplace_back("stateserializermasterchainseqno", td::to_string(state_serializer_masterchain_seqno_)); } - vec.emplace_back("masterchainblock", last_masterchain_block_id_.to_str()); - vec.emplace_back("masterchainblocktime", td::to_string(last_masterchain_block_handle_->unix_time())); - vec.emplace_back("gcmasterchainblock", gc_masterchain_handle_->id().to_str()); - vec.emplace_back("keymasterchainblock", last_key_block_handle_->id().to_str()); - vec.emplace_back("knownkeymasterchainblock", last_known_key_block_handle_->id().to_str()); - vec.emplace_back("rotatemasterchainblock", last_rotate_block_id_.to_str()); - vec.emplace_back("shardclientmasterchainseqno", td::to_string(min_confirmed_masterchain_seqno_)); - vec.emplace_back("stateserializermasterchainseqno", td::to_string(state_serializer_masterchain_seqno_)); - promise.set_value(std::move(vec)); + + if (!shard_client_.empty()) { + auto P = td::PromiseCreator::lambda([promise = merger.make_promise("")](td::Result R) mutable { + if (R.is_error()) { + promise.set_error(R.move_as_error()); + return; + } + std::vector> vec; + vec.emplace_back("shardclientmasterchainseqno", td::to_string(R.move_as_ok())); + promise.set_value(std::move(vec)); + }); + td::actor::send_closure(shard_client_, &ShardClient::get_processed_masterchain_block, std::move(P)); + } + + merger.make_promise("").set_value(std::move(vec)); } td::actor::ActorOwn ValidatorManagerFactory::create( diff --git a/validator/stats-merger.h b/validator/stats-merger.h new file mode 100644 index 00000000..e2c7f242 --- /dev/null +++ b/validator/stats-merger.h @@ -0,0 +1,74 @@ +#pragma once + +#include "td/utils/int_types.h" +#include "td/actor/actor.h" + +namespace ton { + +namespace validator { + +class StatsMerger : public td::actor::Actor { + public: + StatsMerger(td::Promise>> promise) + + : promise_(std::move(promise)) { + } + void start_up() override { + if (!pending_) { + finish(); + } + } + + void finish_subjob(td::Result>> R, std::string prefix) { + if (R.is_ok()) { + auto v = R.move_as_ok(); + for (auto &el : v) { + cur_.emplace_back(prefix + el.first, std::move(el.second)); + } + } + if (--pending_ == 0) { + finish(); + } + } + void inc() { + ++pending_; + } + void dec() { + if (--pending_ == 0) { + finish(); + } + } + void finish() { + promise_.set_value(std::move(cur_)); + stop(); + } + + struct InitGuard { + td::actor::ActorId merger; + ~InitGuard() { + td::actor::send_closure(merger, &StatsMerger::dec); + } + auto make_promise(std::string prefix) { + merger.get_actor_unsafe().inc(); + return td::PromiseCreator::lambda( + [merger = merger, prefix](td::Result>> R) { + td::actor::send_closure(merger, &StatsMerger::finish_subjob, std::move(R), std::move(prefix)); + }); + } + }; + + static InitGuard create(td::Promise>> promise) { + InitGuard ig; + ig.merger = td::actor::create_actor("m", std::move(promise)).release(); + return ig; + } + + private: + std::vector> cur_; + std::atomic pending_{1}; + td::Promise>> promise_; +}; + +} // namespace validator + +} // namespace ton diff --git a/validator/validator-options.hpp b/validator/validator-options.hpp index 2bf00054..68711710 100644 --- a/validator/validator-options.hpp +++ b/validator/validator-options.hpp @@ -125,7 +125,7 @@ struct ValidatorManagerOptionsImpl : public ValidatorManagerOptions { ValidatorManagerOptionsImpl(BlockIdExt zero_block_id, BlockIdExt init_block_id, std::function check_shard, bool allow_blockchain_init, td::ClocksBase::Duration sync_blocks_before, td::ClocksBase::Duration block_ttl, - td::ClocksBase::Duration archive_ttl, td::ClocksBase::Duration state_ttl, + td::ClocksBase::Duration state_ttl, td::ClocksBase::Duration archive_ttl, td::ClocksBase::Duration key_proof_ttl, bool initial_sync_disabled) : zero_block_id_(zero_block_id) , init_block_id_(init_block_id)