Repository: vegaprotocol/vegawallet (go)

Issue:
Add support for proof of work sent from the wallet as part of a transaction
The wallet needs to:
1. generate a unique transaction id
2. use the block hash and additional spam pow params returned by the last block request along with the tx id to generate  proof of work
3. include this in the transaction to vega to verify

reference: https://github.com/vegaprotocol/specs-internal/pull/972/files

Pull request:
feat: Add support for proof of work sent from the wallet as part of a…
… transaction

Closes #504 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 742faf6..2b4e5f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
 ## Unreleased (0.14.0)
 
 ### 🚨 Breaking changes
+- [504](https://github.com/vegaprotocol/vegawallet/pull/504) - Add support for proof of work sent from the wallet as part of a transaction
 - [507](https://github.com/vegaprotocol/vegawallet/issues/507) - Flag `--level` in service run removed
 
 ### 🗑️  Deprecation
diff --git a/Jenkinsfile b/Jenkinsfile
index 5c2d882..25915ce 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -17,7 +17,7 @@ pipeline {
     options {
         skipDefaultCheckout true
         timestamps()
-        timeout(time: 30, unit: 'MINUTES')
+        timeout(time: 45, unit: 'MINUTES')
     }
     parameters {
         string( name: 'VEGA_CORE_BRANCH', defaultValue: '',
diff --git a/cmd/command_send.go b/cmd/command_send.go
index 74df5ed..43cac47 100644
--- a/cmd/command_send.go
+++ b/cmd/command_send.go
@@ -7,7 +7,9 @@ import (
 	"io"
 
 	api "code.vegaprotocol.io/protos/vega/api/v1"
+	commandspb "code.vegaprotocol.io/protos/vega/commands/v1"
 	walletpb "code.vegaprotocol.io/protos/vega/wallet/v1"
+	"code.vegaprotocol.io/shared/libs/crypto"
 	vglog "code.vegaprotocol.io/shared/libs/zap"
 	"code.vegaprotocol.io/vegawallet/cmd/cli"
 	"code.vegaprotocol.io/vegawallet/cmd/flags"
@@ -260,13 +262,14 @@ func SendCommand(w io.Writer, rf *RootFlags, req *SendCommandRequest) error {
 	defer cancelFn()
 
 	log.Info("retrieving block height")
-	blockHeight, err := forwarder.LastBlockHeight(ctx)
+	blockData, cltIdx, err := forwarder.LastBlockHeightAndHash(ctx)
 	if err != nil {
 		return fmt.Errorf("couldn't get last block height: %w", err)
 	}
-	log.Info(fmt.Sprintf("last block height found: %d", blockHeight))
 
-	tx, err := handler.SignTx(req.Wallet, req.Request, blockHeight)
+	log.Info(fmt.Sprintf("last block height found: %d", blockData.Height))
+
+	tx, err := handler.SignTx(req.Wallet, req.Request, blockData.Height)
 	if err != nil {
 		log.Error("couldn't sign transaction", zap.Error(err))
 		return fmt.Errorf("couldn't sign transaction: %w", err)
@@ -274,7 +277,22 @@ func SendCommand(w io.Writer, rf *RootFlags, req *SendCommandRequest) error {
 
 	log.Info("transaction successfully signed", zap.String("signature", tx.Signature.Value))
 
-	txHash, err := forwarder.SendTx(ctx, tx, api.SubmitTransactionRequest_TYPE_ASYNC)
+	// generate a random transaction hash
+	tid := crypto.RandomHash()
+
+	// generate proof of work for the block hash, transaction id and given difficulty required and supported hash function
+	powNonce, _, err := crypto.PoW(blockData.Hash, tid, uint(blockData.SpamPowDifficulty), blockData.SpamPowHashFunction)
+	if err != nil {
+		return fmt.Errorf("couldn't generate proof of work for last block height: %w", err)
+	}
+	tx.Pow = &commandspb.ProofOfWork{
+		Tid:   tid,
+		Nonce: powNonce,
+	}
+
+	log.Info("calculated proof of work for transaction with signature", zap.String("signature", tx.Signature.Value))
+
+	txHash, err := forwarder.SendTx(ctx, tx, api.SubmitTransactionRequest_TYPE_ASYNC, cltIdx)
 	if err != nil {
 		log.Error("couldn't send transaction", zap.Error(err))
 		return fmt.Errorf("couldn't send transaction: %w", err)
diff --git a/cmd/tx_send.go b/cmd/tx_send.go
index 293ac47..eee328e 100644
--- a/cmd/tx_send.go
+++ b/cmd/tx_send.go
@@ -197,7 +197,7 @@ func SendTx(w io.Writer, rf *RootFlags, req *SendTxRequest) error {
 	ctx, cancelFn := context.WithTimeout(context.Background(), ForwarderRequestTimeout)
 	defer cancelFn()
 
-	txHash, err := forwarder.SendTx(ctx, req.Tx, api.SubmitTransactionRequest_TYPE_ASYNC)
+	txHash, err := forwarder.SendTx(ctx, req.Tx, api.SubmitTransactionRequest_TYPE_ASYNC, -1)
 	if err != nil {
 		log.Error("Couldn't send transaction", zap.Error(err))
 		return fmt.Errorf("couldn't send transaction: %w", err)
diff --git a/go.mod b/go.mod
index 7908cc8..e72703f 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,8 @@ module code.vegaprotocol.io/vegawallet
 go 1.17
 
 require (
-	code.vegaprotocol.io/protos v0.49.1
-	code.vegaprotocol.io/shared v0.0.0-20220202150846-b6aba31dcdb0
+	code.vegaprotocol.io/protos v0.49.3-0.20220318095248-d80bc0e691ff
+	code.vegaprotocol.io/shared v0.0.0-20220316120021-93b67baf439a
 	github.com/blang/semver/v4 v4.0.0
 	github.com/cenkalti/backoff/v4 v4.0.2
 	github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1
@@ -20,9 +20,9 @@ require (
 	github.com/tyler-smith/go-bip39 v1.1.0
 	github.com/vegaprotocol/go-slip10 v0.1.0
 	go.uber.org/zap v1.20.0
-	golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
-	golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
-	google.golang.org/grpc v1.43.0
+	golang.org/x/sys v0.0.0-20220209214540-3681064d5158
+	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
+	google.golang.org/grpc v1.45.0
 )
 
 require (
@@ -39,10 +39,10 @@ require (
 	github.com/spf13/pflag v1.0.5 // indirect
 	go.uber.org/atomic v1.7.0 // indirect
 	go.uber.org/multierr v1.6.0 // indirect
-	golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
-	golang.org/x/net v0.0.0-20210610132358-84b48f89b13b // indirect
-	golang.org/x/text v0.3.6 // indirect
-	google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 // indirect
+	golang.org/x/crypto v0.0.0-20220314234724-5d542ad81a58 // indirect
+	golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
+	golang.org/x/text v0.3.7 // indirect
+	google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
 	google.golang.org/protobuf v1.27.1 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 )
diff --git a/go.sum b/go.sum
index 2cc6bdb..734860c 100644
--- a/go.sum
+++ b/go.sum
@@ -31,10 +31,10 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-code.vegaprotocol.io/protos v0.49.1 h1:U7apDIlcok9OSJsLSNoQnQdV4QDOyS2smZbKLfgESUU=
-code.vegaprotocol.io/protos v0.49.1/go.mod h1:sGGn35s31oBNLNCC5tFiKtfNEPiKm3UghOKNaO+bq58=
-code.vegaprotocol.io/shared v0.0.0-20220202150846-b6aba31dcdb0 h1:vDjKTd508FwIwoSdZBEvtEfmBeaYdodgLM96v253zKI=
-code.vegaprotocol.io/shared v0.0.0-20220202150846-b6aba31dcdb0/go.mod h1:pNHKwqRDkotUN0s6jErl7Eb2EpZa2HAf03lyPMTCUT0=
+code.vegaprotocol.io/protos v0.49.3-0.20220318095248-d80bc0e691ff h1:k72WOTHn71WlsjMa+heHptdgKwWdS4LeMzDXH1lt9e0=
+code.vegaprotocol.io/protos v0.49.3-0.20220318095248-d80bc0e691ff/go.mod h1:K6FWDdxWzEgZjz2G1f8gmrSPMey720ryHnzNRn1pW7w=
+code.vegaprotocol.io/shared v0.0.0-20220316120021-93b67baf439a h1:yXjs1EerIfaQ7soXgLcoapLR+Y1J8OUFlmIBaYS2URc=
+code.vegaprotocol.io/shared v0.0.0-20220316120021-93b67baf439a/go.mod h1:pNHKwqRDkotUN0s6jErl7Eb2EpZa2HAf03lyPMTCUT0=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
@@ -575,8 +575,9 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220314234724-5d542ad81a58 h1:L8CkJyVoa0/NslN3RUMLgasK5+KatNvyRGQ9QyCYAfc=
+golang.org/x/crypto v0.0.0-20220314234724-5d542ad81a58/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -652,8 +653,9 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A=
-golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -726,20 +728,23 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
-golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -858,8 +863,9 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201111145450-ac7456db90a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201119123407-9b1e624d6bc4/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 h1:zzNejm+EgrbLfDZ6lu9Uud2IVvHySPl8vQzf04laR5Q=
 google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 h1:ErU+UA6wxadoU8nWrsy5MZUVBs75K17zUCsUCIfrXCE=
+google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -886,8 +892,9 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
-google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
 google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
+google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
diff --git a/node/forwarder.go b/node/forwarder.go
index b9d5404..9a4bde4 100644
--- a/node/forwarder.go
+++ b/node/forwarder.go
@@ -63,7 +63,7 @@ func (n *Forwarder) HealthCheck(ctx context.Context) error {
 	req := api.GetVegaTimeRequest{}
 	return backoff.Retry(
 		func() error {
-			clt := n.nextClt()
+			clt := n.clts[n.nextClt()]
 			resp, err := clt.GetVegaTime(ctx, &req)
 			if err != nil {
 				return err
@@ -75,18 +75,21 @@ func (n *Forwarder) HealthCheck(ctx context.Context) error {
 	)
 }
 
-func (n *Forwarder) LastBlockHeight(ctx context.Context) (uint64, error) {
+// LastBlockHeightAndHash returns information about the last block from vega and the node it used to fetch it.
+func (n *Forwarder) LastBlockHeightAndHash(ctx context.Context) (*api.LastBlockHeightResponse, int, error) {
 	req := api.LastBlockHeightRequest{}
-	var height uint64
+	var resp *api.LastBlockHeightResponse
+	clt := -1
 	err := backoff.Retry(
 		func() error {
-			clt := n.nextClt()
-			resp, err := clt.LastBlockHeight(ctx, &req)
+			clt = n.nextClt()
+			r, err := n.clts[clt].LastBlockHeight(ctx, &req)
 			if err != nil {
 				n.log.Debug("Couldn't get last block", zap.Error(err))
 				return err
 			}
-			height = resp.Height
+			resp = r
+			n.log.Info("", zap.Uint64("block-height", r.Height), zap.String("block-hash", r.Hash), zap.Uint32("difficulty", r.SpamPowDifficulty), zap.String("function", r.SpamPowHashFunction))
 			return nil
 		},
 		backoff.WithMaxRetries(backoff.NewExponentialBackOff(), n.nodeCfgs.Retries),
@@ -94,25 +97,29 @@ func (n *Forwarder) LastBlockHeight(ctx context.Context) (uint64, error) {
 
 	if err != nil {
 		n.log.Error("Couldn't get last block", zap.Error(err))
+		clt = -1
 	} else {
 		n.log.Debug("Last block when sending transaction",
 			zap.Time("request.time", time.Now()),
-			zap.Uint64("block.height", height),
+			zap.Uint64("block.height", resp.Height),
 		)
 	}
 
-	return height, err
+	return resp, clt, err
 }
 
-func (n *Forwarder) SendTx(ctx context.Context, tx *commandspb.Transaction, ty api.SubmitTransactionRequest_Type) (string, error) {
+func (n *Forwarder) SendTx(ctx context.Context, tx *commandspb.Transaction, ty api.SubmitTransactionRequest_Type, cltIdx int) (string, error) {
 	req := api.SubmitTransactionRequest{
 		Tx:   tx,
 		Type: ty,
 	}
 	var resp *api.SubmitTransactionResponse
+	if cltIdx < 0 {
+		cltIdx = n.nextClt()
+	}
 	err := backoff.Retry(
 		func() error {
-			clt := n.nextClt()
+			clt := n.clts[cltIdx]
 			r, err := clt.SubmitTransaction(ctx, &req)
 			if err != nil {
 				n.log.Error("Couldn't send transaction", zap.Error(err))
@@ -133,10 +140,10 @@ func (n *Forwarder) SendTx(ctx context.Context, tx *commandspb.Transaction, ty a
 	return resp.TxHash, nil
 }
 
-func (n *Forwarder) nextClt() api.CoreServiceClient {
+func (n *Forwarder) nextClt() int {
 	i := atomic.AddUint64(&n.next, 1)
 	n.log.Info("Sending transaction to Vega node",
 		zap.String("host", n.nodeCfgs.Hosts[(int(i)-1)%len(n.clts)]),
 	)
-	return n.clts[(int(i)-1)%len(n.clts)]
+	return (int(i) - 1) % len(n.clts)
 }
diff --git a/service/errors.go b/service/errors.go
index d34dfae..2df580e 100644
--- a/service/errors.go
+++ b/service/errors.go
@@ -14,6 +14,7 @@ var (
 	ErrCouldNotGetBlockHeight = errors.New("couldn't get last block height")
 	ErrShouldBeBase64Encoded  = errors.New("should be base64 encoded")
 	ErrRSAKeysAlreadyExists   = errors.New("RSA keys already exist")
+	ErrCouldNotGetPoW         = errors.New("could not get proof of work")
 )
 
 type ErrorsResponse struct {
diff --git a/service/mocks/node_forward_mock.go b/service/mocks/node_forward_mock.go
index 88980cb..618f65a 100644
--- a/service/mocks/node_forward_mock.go
+++ b/service/mocks/node_forward_mock.go
@@ -5,37 +5,38 @@
 package mocks
 
 import (
+	context "context"
+	reflect "reflect"
+
 	v1 "code.vegaprotocol.io/protos/vega/api/v1"
 	v10 "code.vegaprotocol.io/protos/vega/commands/v1"
-	context "context"
 	gomock "github.com/golang/mock/gomock"
-	reflect "reflect"
 )
 
-// MockNodeForward is a mock of NodeForward interface
+// MockNodeForward is a mock of NodeForward interface.
 type MockNodeForward struct {
 	ctrl     *gomock.Controller
 	recorder *MockNodeForwardMockRecorder
 }
 
-// MockNodeForwardMockRecorder is the mock recorder for MockNodeForward
+// MockNodeForwardMockRecorder is the mock recorder for MockNodeForward.
 type MockNodeForwardMockRecorder struct {
 	mock *MockNodeForward
 }
 
-// NewMockNodeForward creates a new mock instance
+// NewMockNodeForward creates a new mock instance.
 func NewMockNodeForward(ctrl *gomock.Controller) *MockNodeForward {
 	mock := &MockNodeForward{ctrl: ctrl}
 	mock.recorder = &MockNodeForwardMockRecorder{mock}
 	return mock
 }
 
-// EXPECT returns an object that allows the caller to indicate expected use
+// EXPECT returns an object that allows the caller to indicate expected use.
 func (m *MockNodeForward) EXPECT() *MockNodeForwardMockRecorder {
 	return m.recorder
 }
 
-// HealthCheck mocks base method
+// HealthCheck mocks base method.
 func (m *MockNodeForward) HealthCheck(arg0 context.Context) error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "HealthCheck", arg0)
@@ -43,38 +44,39 @@ func (m *MockNodeForward) HealthCheck(arg0 context.Context) error {
 	return ret0
 }
 
-// HealthCheck indicates an expected call of HealthCheck
+// HealthCheck indicates an expected call of HealthCheck.
 func (mr *MockNodeForwardMockRecorder) HealthCheck(arg0 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockNodeForward)(nil).HealthCheck), arg0)
 }
 
-// LastBlockHeight mocks base method
-func (m *MockNodeForward) LastBlockHeight(arg0 context.Context) (uint64, error) {
+// LastBlockHeightAndHash mocks base method.
+func (m *MockNodeForward) LastBlockHeightAndHash(arg0 context.Context) (*v1.LastBlockHeightResponse, int, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "LastBlockHeight", arg0)
-	ret0, _ := ret[0].(uint64)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
+	ret := m.ctrl.Call(m, "LastBlockHeightAndHash", arg0)
+	ret0, _ := ret[0].(*v1.LastBlockHeightResponse)
+	ret1, _ := ret[1].(int)
+	ret2, _ := ret[2].(error)
+	return ret0, ret1, ret2
 }
 
-// LastBlockHeight indicates an expected call of LastBlockHeight
-func (mr *MockNodeForwardMockRecorder) LastBlockHeight(arg0 interface{}) *gomock.Call {
+// LastBlockHeightAndHash indicates an expected call of LastBlockHeightAndHash.
+func (mr *MockNodeForwardMockRecorder) LastBlockHeightAndHash(arg0 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LastBlockHeight", reflect.TypeOf((*MockNodeForward)(nil).LastBlockHeight), arg0)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LastBlockHeightAndHash", reflect.TypeOf((*MockNodeForward)(nil).LastBlockHeightAndHash), arg0)
 }
 
-// SendTx mocks base method
-func (m *MockNodeForward) SendTx(arg0 context.Context, arg1 *v10.Transaction, arg2 v1.SubmitTransactionRequest_Type) (string, error) {
+// SendTx mocks base method.
+func (m *MockNodeForward) SendTx(arg0 context.Context, arg1 *v10.Transaction, arg2 v1.SubmitTransactionRequest_Type, arg3 int) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "SendTx", arg0, arg1, arg2)
+	ret := m.ctrl.Call(m, "SendTx", arg0, arg1, arg2, arg3)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
-// SendTx indicates an expected call of SendTx
-func (mr *MockNodeForwardMockRecorder) SendTx(arg0, arg1, arg2 interface{}) *gomock.Call {
+// SendTx indicates an expected call of SendTx.
+func (mr *MockNodeForwardMockRecorder) SendTx(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTx", reflect.TypeOf((*MockNodeForward)(nil).SendTx), arg0, arg1, arg2)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTx", reflect.TypeOf((*MockNodeForward)(nil).SendTx), arg0, arg1, arg2, arg3)
 }
diff --git a/service/service.go b/service/service.go
index f16b760..e07e701 100644
--- a/service/service.go
+++ b/service/service.go
@@ -15,6 +15,7 @@ import (
 	api "code.vegaprotocol.io/protos/vega/api/v1"
 	commandspb "code.vegaprotocol.io/protos/vega/commands/v1"
 	walletpb "code.vegaprotocol.io/protos/vega/wallet/v1"
+	"code.vegaprotocol.io/shared/libs/crypto"
 	wcommands "code.vegaprotocol.io/vegawallet/commands"
 	"code.vegaprotocol.io/vegawallet/network"
 	"code.vegaprotocol.io/vegawallet/version"
@@ -404,9 +405,9 @@ type Auth interface {
 // NodeForward ...
 //go:generate go run github.com/golang/mock/mockgen -destination mocks/node_forward_mock.go -package mocks code.vegaprotocol.io/vegawallet/service NodeForward
 type NodeForward interface {
-	SendTx(context.Context, *commandspb.Transaction, api.SubmitTransactionRequest_Type) (string, error)
+	SendTx(context.Context, *commandspb.Transaction, api.SubmitTransactionRequest_Type, int) (string, error)
 	HealthCheck(context.Context) error
-	LastBlockHeight(context.Context) (uint64, error)
+	LastBlockHeightAndHash(context.Context) (*api.LastBlockHeightResponse, int, error)
 }
 
 func NewService(log *zap.Logger, net *network.Network, h WalletHandler, a Auth, n NodeForward) (*Service, error) {
@@ -725,7 +726,7 @@ func (s *Service) signTx(token string, w http.ResponseWriter, r *http.Request, _
 		return
 	}
 
-	height, err := s.nodeForward.LastBlockHeight(r.Context())
+	blockData, cltIdx, err := s.nodeForward.LastBlockHeightAndHash(r.Context())
 	if err != nil {
 		s.writeInternalError(w, ErrCouldNotGetBlockHeight)
 		return
@@ -737,7 +738,7 @@ func (s *Service) signTx(token string, w http.ResponseWriter, r *http.Request, _
 		return
 	}
 
-	tx, err := s.handler.SignTx(name, req, height)
+	tx, err := s.handler.SignTx(name, req, blockData.Height)
 	if err != nil {
 		s.writeInternalError(w, err)
 		return
@@ -748,7 +749,19 @@ func (s *Service) signTx(token string, w http.ResponseWriter, r *http.Request, _
 		return
 	}
 
-	txHash, err := s.nodeForward.SendTx(r.Context(), tx, ty)
+	// generate proof of work for the transaction
+	tid := crypto.RandomHash()
+	powNonce, _, err := crypto.PoW(blockData.Hash, tid, uint(blockData.SpamPowDifficulty), crypto.Sha3)
+	if err != nil {
+		s.writeInternalError(w, err)
+		return
+	}
+	tx.Pow = &commandspb.ProofOfWork{
+		Tid:   tid,
+		Nonce: powNonce,
+	}
+
+	txHash, err := s.nodeForward.SendTx(r.Context(), tx, ty, cltIdx)
 	if err != nil {
 		if st, ok := status.FromError(err); ok {
 			var details []string


Test patch:
diff --git a/service/service_test.go b/service/service_test.go
index 9e0500e..c88a84f 100644
--- a/service/service_test.go
+++ b/service/service_test.go
@@ -687,8 +687,13 @@ func testSigningTransactionSucceeds(t *testing.T) {
 	// setup
 	s.auth.EXPECT().VerifyToken(token).Times(1).Return(walletName, nil)
 	s.handler.EXPECT().SignTx(walletName, gomock.Any(), gomock.Any()).Times(1).Return(&commandspb.Transaction{}, nil)
-	s.nodeForward.EXPECT().SendTx(gomock.Any(), &commandspb.Transaction{}, api.SubmitTransactionRequest_TYPE_ASYNC).Times(0)
-	s.nodeForward.EXPECT().LastBlockHeight(gomock.Any()).Times(1).Return(uint64(42), nil)
+	s.nodeForward.EXPECT().SendTx(gomock.Any(), &commandspb.Transaction{}, api.SubmitTransactionRequest_TYPE_ASYNC, gomock.Any()).Times(0)
+	s.nodeForward.EXPECT().LastBlockHeightAndHash(gomock.Any()).Times(1).Return(&api.LastBlockHeightResponse{
+		Height:              42,
+		Hash:                "0292041e2f0cf741894503fb3ead4cb817bca2375e543aa70f7c4d938157b5a6",
+		SpamPowDifficulty:   2,
+		SpamPowHashFunction: "sha3_24_rounds",
+	}, 0, nil)
 
 	// when
 	statusCode, _ := serveHTTP(t, s, signTxRequest(t, payload, headers))
@@ -710,8 +715,13 @@ func testSigningTransactionWithPropagationSucceeds(t *testing.T) {
 	// setup
 	s.auth.EXPECT().VerifyToken(token).Times(1).Return(walletName, nil)
 	s.handler.EXPECT().SignTx(walletName, gomock.Any(), gomock.Any()).Times(1).Return(&commandspb.Transaction{}, nil)
-	s.nodeForward.EXPECT().SendTx(gomock.Any(), &commandspb.Transaction{}, api.SubmitTransactionRequest_TYPE_ASYNC).Times(1)
-	s.nodeForward.EXPECT().LastBlockHeight(gomock.Any()).Times(1).Return(uint64(42), nil)
+	s.nodeForward.EXPECT().SendTx(gomock.Any(), gomock.Any(), api.SubmitTransactionRequest_TYPE_ASYNC, gomock.Any()).Times(1)
+	s.nodeForward.EXPECT().LastBlockHeightAndHash(gomock.Any()).Times(1).Return(&api.LastBlockHeightResponse{
+		Height:              42,
+		Hash:                "0292041e2f0cf741894503fb3ead4cb817bca2375e543aa70f7c4d938157b5a6",
+		SpamPowDifficulty:   2,
+		SpamPowHashFunction: "sha3_24_rounds",
+	}, 0, nil)
 
 	// when
 	statusCode, _ := serveHTTP(t, s, signTxRequest(t, payload, headers))
@@ -733,8 +743,13 @@ func testSigningTransactionWithFailedPropagationFails(t *testing.T) {
 	// setup
 	s.auth.EXPECT().VerifyToken(token).Times(1).Return(walletName, nil)
 	s.handler.EXPECT().SignTx(walletName, gomock.Any(), gomock.Any()).Times(1).Return(&commandspb.Transaction{}, nil)
-	s.nodeForward.EXPECT().SendTx(gomock.Any(), &commandspb.Transaction{}, api.SubmitTransactionRequest_TYPE_ASYNC).Times(1).Return("", assert.AnError)
-	s.nodeForward.EXPECT().LastBlockHeight(gomock.Any()).Times(1).Return(uint64(42), nil)
+	s.nodeForward.EXPECT().SendTx(gomock.Any(), gomock.Any(), api.SubmitTransactionRequest_TYPE_ASYNC, gomock.Any()).Times(1).Return("", assert.AnError)
+	s.nodeForward.EXPECT().LastBlockHeightAndHash(gomock.Any()).Times(1).Return(&api.LastBlockHeightResponse{
+		Height:              42,
+		Hash:                "0292041e2f0cf741894503fb3ead4cb817bca2375e543aa70f7c4d938157b5a6",
+		SpamPowDifficulty:   2,
+		SpamPowHashFunction: "sha3_24_rounds",
+	}, 0, nil)
 
 	// when
 	statusCode, _ := serveHTTP(t, s, signTxRequest(t, payload, headers))
@@ -756,8 +771,13 @@ func testFailedTransactionSigningFails(t *testing.T) {
 	// setup
 	s.auth.EXPECT().VerifyToken(token).Times(1).Return(walletName, nil)
 	s.handler.EXPECT().SignTx(walletName, gomock.Any(), gomock.Any()).Times(1).Return(nil, assert.AnError)
-	s.nodeForward.EXPECT().SendTx(gomock.Any(), &commandspb.Transaction{}, api.SubmitTransactionRequest_TYPE_ASYNC).Times(0)
-	s.nodeForward.EXPECT().LastBlockHeight(gomock.Any()).Times(1).Return(uint64(42), nil)
+	s.nodeForward.EXPECT().SendTx(gomock.Any(), &commandspb.Transaction{}, api.SubmitTransactionRequest_TYPE_ASYNC, gomock.Any()).Times(0)
+	s.nodeForward.EXPECT().LastBlockHeightAndHash(gomock.Any()).Times(1).Return(&api.LastBlockHeightResponse{
+		Height:              42,
+		Hash:                "0292041e2f0cf741894503fb3ead4cb817bca2375e543aa70f7c4d938157b5a6",
+		SpamPowDifficulty:   2,
+		SpamPowHashFunction: "sha3_24_rounds",
+	}, 0, nil)
 
 	// when
 	statusCode, _ := serveHTTP(t, s, signTxRequest(t, payload, headers))
diff --git a/wallets/handler_test.go b/wallets/handler_test.go
index 0023d9c..d512859 100644
--- a/wallets/handler_test.go
+++ b/wallets/handler_test.go
@@ -1277,7 +1277,7 @@ func testHandlerSigningTxSucceeds(t *testing.T) {
 
 	// then
 	require.NoError(t, err)
-	assert.Equal(t, uint32(1), tx.Version)
+	assert.Equal(t, uint32(2), tx.Version)
 	assert.NotEmpty(t, tx.From)
 	assert.Equal(t, tx.GetPubKey(), pubKey)
 	assert.NotEmpty(t, tx.InputData)