From e6f6e4375435286137ce352b9785d729724ca712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Eckel?= Date: Fri, 8 Mar 2024 10:54:52 +0100 Subject: [PATCH] fixed crashes when two machines are attested at the same time (#343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * * added creation of random machines to prepare a test case * setting all the consensus timeout values at once (if changed) * mutex protection of the elements tx crafting methods (sequential processing) * Extending the TestMachineNFTIssuance test case to parallel threads and threading issues * moving all elements-rpc usage to the elementd-connector.go file * removed call to fatal * added WaitForNextBlock to be out of sync with PoP to avoid the following error PoP broadcast tx failed: node0.info: key not found after the test --------- Signed-off-by: Jürgen Eckel --- tests/e2e/machine/suite.go | 1 + testutil/moduleobject/sampleobject.go | 36 ++++++++- testutil/network/network.go | 3 + util/elementsd_connector.go | 110 +++++++++++++++++++++++++- util/machine_nft.go | 95 ---------------------- util/machine_nft_test.go | 25 ++++-- util/sync.go | 4 +- 7 files changed, 170 insertions(+), 104 deletions(-) diff --git a/tests/e2e/machine/suite.go b/tests/e2e/machine/suite.go index cf81d44..c667551 100644 --- a/tests/e2e/machine/suite.go +++ b/tests/e2e/machine/suite.go @@ -184,6 +184,7 @@ func (s *E2ETestSuite) TestMachineAllowanceAttestation() { s.Require().NoError(s.network.WaitForNextBlock()) s.Require().NoError(s.network.WaitForNextBlock()) s.Require().NoError(s.network.WaitForNextBlock()) + s.Require().NoError(s.network.WaitForNextBlock()) // reset clientCtx to validator ctx libConfig.SetClientCtx(val.ClientCtx) diff --git a/testutil/moduleobject/sampleobject.go b/testutil/moduleobject/sampleobject.go index 63778df..ce2ef81 100644 --- a/testutil/moduleobject/sampleobject.go +++ b/testutil/moduleobject/sampleobject.go @@ -2,6 +2,7 @@ package moduleobject import ( "encoding/hex" + "strconv" "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/chaincfg" @@ -51,7 +52,40 @@ func Machine(name, pubKey string, prvKey string, address string) machinetypes.Ma m := machinetypes.Machine{ Name: name, Ticker: name + "_ticker", - Domain: "lab.r3c.network", + Domain: "testnet-assets.rddl.iok", + Reissue: true, + Amount: 1000, + Precision: 8, + IssuerPlanetmint: planetmintPubKey, + IssuerLiquid: liquidPubKey, + MachineId: pubKey, + Metadata: &metadata, + Type: 1, + MachineIdSignature: signatureHex, + Address: address, + } + return m +} + +func MachineRandom(name, pubKey string, prvKey string, address string, random int) machinetypes.Machine { + metadata := Metadata() + _, liquidPubKey := ExtendedKeyPair(config.LiquidNetParams) + _, planetmintPubKey := ExtendedKeyPair(config.PlmntNetParams) + + prvKeyBytes, _ := hex.DecodeString(prvKey) + sk := &secp256k1.PrivKey{Key: prvKeyBytes} + pubKeyBytes, _ := hex.DecodeString(pubKey) + sign, _ := sk.Sign(pubKeyBytes) + signatureHex := hex.EncodeToString(sign) + + if address == "" { + address = sample.Secp256k1AccAddress().String() + } + + m := machinetypes.Machine{ + Name: name + strconv.Itoa(random), + Ticker: name + strconv.Itoa(random) + "_ticker", + Domain: "testnet-assets.rddl.io", Reissue: true, Amount: 1000, Precision: 8, diff --git a/testutil/network/network.go b/testutil/network/network.go index f9174ad..0703555 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -334,6 +334,9 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) { ctx := server.NewDefaultContext() tmCfg := ctx.Config tmCfg.Consensus.TimeoutCommit = cfg.TimeoutCommit + tmCfg.Consensus.TimeoutPrecommit = cfg.TimeoutCommit + tmCfg.Consensus.TimeoutPrevote = cfg.TimeoutCommit + tmCfg.Consensus.TimeoutPropose = cfg.TimeoutCommit // Only allow the first validator to expose an RPC, API and gRPC // server/client due to Tendermint in-process constraints. diff --git a/util/elementsd_connector.go b/util/elementsd_connector.go index 3bfaef0..e6021b4 100644 --- a/util/elementsd_connector.go +++ b/util/elementsd_connector.go @@ -1,17 +1,29 @@ package util import ( + "crypto/sha256" + "encoding/json" + "fmt" "strings" + "sync" "github.com/planetmint/planetmint-go/config" + "github.com/planetmint/planetmint-go/x/machine/types" elements "github.com/rddl-network/elements-rpc" ) +var ( + // this mutex has to protect all signing and crafting of transactions and their inputs + // so that UTXOs are not spend twice by accident + elementsSyncAccess sync.Mutex +) + func ReissueAsset(reissueTx string) (txID string, err error) { conf := config.GetConfig() url := conf.GetRPCURL() cmdArgs := strings.Split(reissueTx, " ") - + elementsSyncAccess.Lock() + defer elementsSyncAccess.Unlock() result, err := elements.ReissueAsset(url, []string{cmdArgs[1], cmdArgs[2]}) if err != nil { return @@ -24,6 +36,8 @@ func DistributeAsset(address string, amount string, reissuanceAsset string) (txI conf := config.GetConfig() url := conf.GetRPCURL() + elementsSyncAccess.Lock() + defer elementsSyncAccess.Unlock() txID, err = elements.SendToAddress(url, []string{ address, `"` + amount + `"`, @@ -38,3 +52,97 @@ func DistributeAsset(address string, amount string, reissuanceAsset string) (txI }) return } + +func IssueNFTAsset(name string, machineAddress string, domain string) (assetID string, contract string, hexTx string, err error) { + conf := config.GetConfig() + url := conf.GetRPCURL() + + address, err := elements.GetNewAddress(url, []string{``}) + if err != nil { + return + } + + addressInfo, err := elements.GetAddressInfo(url, []string{address}) + if err != nil { + return + } + + elementsSyncAccess.Lock() + defer elementsSyncAccess.Unlock() + hex, err := elements.CreateRawTransaction(url, []string{`[]`, `[{"data":"00"}]`}) + if err != nil { + return + } + + fundRawTransactionResult, err := elements.FundRawTransaction(url, []string{hex, `{"feeRate":0.00001000}`}) + if err != nil { + return + } + + c := types.Contract{ + Entity: types.Entity{ + Domain: domain, + }, + IssuerPubkey: addressInfo.Pubkey, + MachineAddr: machineAddress, + Name: name, + Precision: 0, + Version: 0, + } + contractBytes, err := json.Marshal(c) + if err != nil { + return + } + // e.g. {"entity":{"domain":"testnet-assets.rddl.io"}, "issuer_pubkey":"02...} + contract = string(contractBytes) + + h := sha256.New() + _, err = h.Write(contractBytes) + if err != nil { + return + } + // e.g. 7ca8bb403ee5dccddef7b89b163048cf39439553f0402351217a4a03d2224df8 + hash := h.Sum(nil) + + // Reverse hash, e.g. f84d22d2034a7a21512340f053954339cf4830169bb8f7decddce53e40bba87c + for i, j := 0, len(hash)-1; i < j; i, j = i+1, j-1 { + hash[i], hash[j] = hash[j], hash[i] + } + + rawIssueAssetResults, err := elements.RawIssueAsset(url, []string{fundRawTransactionResult.Hex, + `[{"asset_amount":0.00000001, "asset_address":"` + address + `", "blind":false, "contract_hash":"` + fmt.Sprintf("%+x", hash) + `"}]`, + }) + if err != nil { + return + } + + rawIssueAssetResult := rawIssueAssetResults[len(rawIssueAssetResults)-1] + hex, err = elements.BlindRawTransaction(url, []string{rawIssueAssetResult.Hex, `true`, `[]`, `false`}) + if err != nil { + return + } + assetID = rawIssueAssetResult.Asset + + signRawTransactionWithWalletResult, err := elements.SignRawTransactionWithWallet(url, []string{hex}) + if err != nil { + return + } + + testMempoolAcceptResults, err := elements.TestMempoolAccept(url, []string{`["` + signRawTransactionWithWalletResult.Hex + `"]`}) + if err != nil { + return + } + + testMempoolAcceptResult := testMempoolAcceptResults[len(testMempoolAcceptResults)-1] + if !testMempoolAcceptResult.Allowed { + err = fmt.Errorf("not accepted by mempool: %+v %+v", testMempoolAcceptResult, signRawTransactionWithWalletResult) + return + } + + hex, err = elements.SendRawTransaction(url, []string{signRawTransactionWithWalletResult.Hex}) + if err != nil { + return + } + + return assetID, contract, hex, err +} diff --git a/util/machine_nft.go b/util/machine_nft.go index 87000dd..28eba1c 100644 --- a/util/machine_nft.go +++ b/util/machine_nft.go @@ -3,20 +3,16 @@ package util import ( "bytes" "context" - "crypto/sha256" "encoding/json" "fmt" "io" - "log" "net/http" "strconv" "strings" errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - config "github.com/planetmint/planetmint-go/config" "github.com/planetmint/planetmint-go/x/machine/types" - elements "github.com/rddl-network/elements-rpc" ) type HTTPClient interface { @@ -31,97 +27,6 @@ func init() { RegisterAssetServiceHTTPClient = &http.Client{} } -func IssueNFTAsset(name string, machineAddress string, domain string) (assetID string, contract string, hexTx string, err error) { - conf := config.GetConfig() - - url := conf.GetRPCURL() - address, err := elements.GetNewAddress(url, []string{``}) - if err != nil { - return - } - - addressInfo, err := elements.GetAddressInfo(url, []string{address}) - if err != nil { - return - } - - hex, err := elements.CreateRawTransaction(url, []string{`[]`, `[{"data":"00"}]`}) - if err != nil { - return - } - - fundRawTransactionResult, err := elements.FundRawTransaction(url, []string{hex, `{"feeRate":0.00001000}`}) - if err != nil { - return - } - - c := types.Contract{ - Entity: types.Entity{ - Domain: domain, - }, - IssuerPubkey: addressInfo.Pubkey, - MachineAddr: machineAddress, - Name: name, - Precision: 0, - Version: 0, - } - contractBytes, err := json.Marshal(c) - if err != nil { - return - } - // e.g. {"entity":{"domain":"testnet-assets.rddl.io"}, "issuer_pubkey":"02...} - contract = string(contractBytes) - - h := sha256.New() - _, err = h.Write(contractBytes) - if err != nil { - return - } - // e.g. 7ca8bb403ee5dccddef7b89b163048cf39439553f0402351217a4a03d2224df8 - hash := h.Sum(nil) - - // Reverse hash, e.g. f84d22d2034a7a21512340f053954339cf4830169bb8f7decddce53e40bba87c - for i, j := 0, len(hash)-1; i < j; i, j = i+1, j-1 { - hash[i], hash[j] = hash[j], hash[i] - } - - rawIssueAssetResults, err := elements.RawIssueAsset(url, []string{fundRawTransactionResult.Hex, - `[{"asset_amount":0.00000001, "asset_address":"` + address + `", "blind":false, "contract_hash":"` + fmt.Sprintf("%+x", hash) + `"}]`, - }) - if err != nil { - return - } - - rawIssueAssetResult := rawIssueAssetResults[len(rawIssueAssetResults)-1] - hex, err = elements.BlindRawTransaction(url, []string{rawIssueAssetResult.Hex, `true`, `[]`, `false`}) - if err != nil { - return - } - assetID = rawIssueAssetResult.Asset - - signRawTransactionWithWalletResult, err := elements.SignRawTransactionWithWallet(url, []string{hex}) - if err != nil { - return - } - - testMempoolAcceptResults, err := elements.TestMempoolAccept(url, []string{`["` + signRawTransactionWithWalletResult.Hex + `"]`}) - if err != nil { - return - } - - testMempoolAcceptResult := testMempoolAcceptResults[len(testMempoolAcceptResults)-1] - if !testMempoolAcceptResult.Allowed { - log.Fatalln("not accepted by mempool") - } - - hex, err = elements.SendRawTransaction(url, []string{signRawTransactionWithWalletResult.Hex}) - if err != nil { - return - } - - return assetID, contract, hex, err -} - func IssueMachineNFT(goCtx context.Context, machine *types.Machine, scheme string, domain string, path string) error { ctx := sdk.UnwrapSDKContext(goCtx) // asset registration is in order to have the contact published diff --git a/util/machine_nft_test.go b/util/machine_nft_test.go index d9a086b..e494f6e 100644 --- a/util/machine_nft_test.go +++ b/util/machine_nft_test.go @@ -3,6 +3,9 @@ package util_test import ( "context" "encoding/json" + "math/rand" + "strconv" + "sync" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -47,11 +50,21 @@ func TestMachineNFTIssuance(t *testing.T) { elements.Client = &elementsmocks.MockClient{} util.RegisterAssetServiceHTTPClient = &mocks.MockClient{} _, ctx := keeper.MachineKeeper(t) - sk, pk := sample.KeyPair() - machine := moduleobject.Machine(pk, pk, sk, "") - goCtx := sdk.WrapSDKContext(ctx) - params := types.DefaultParams() - err := util.IssueMachineNFT(goCtx, &machine, params.AssetRegistryScheme, params.AssetRegistryDomain, params.AssetRegistryPath) - assert.NoError(t, err) + var wg sync.WaitGroup + + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + randomInt := rand.Int() + sk, pk := sample.KeyPair(randomInt) + machine := moduleobject.MachineRandom(pk, pk, sk, "address "+strconv.Itoa(randomInt), randomInt) + goCtx := sdk.WrapSDKContext(ctx) + + err := util.IssueMachineNFT(goCtx, &machine, params.AssetRegistryScheme, params.AssetRegistryDomain, params.AssetRegistryPath) + assert.NoError(t, err) + wg.Done() + }() + } + wg.Wait() } diff --git a/util/sync.go b/util/sync.go index ccb3386..2d51b25 100644 --- a/util/sync.go +++ b/util/sync.go @@ -2,4 +2,6 @@ package util import "sync" -var TerminationWaitGroup sync.WaitGroup +var ( + TerminationWaitGroup sync.WaitGroup +)