fixed crashes when two machines are attested at the same time (#343)

* * 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 <juergen@riddleandcode.com>
This commit is contained in:
Jürgen Eckel 2024-03-08 10:54:52 +01:00 committed by GitHub
parent 8a5e7b5da6
commit e6f6e43754
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 170 additions and 104 deletions

View File

@ -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())
s.Require().NoError(s.network.WaitForNextBlock()) s.Require().NoError(s.network.WaitForNextBlock())
s.Require().NoError(s.network.WaitForNextBlock())
// reset clientCtx to validator ctx // reset clientCtx to validator ctx
libConfig.SetClientCtx(val.ClientCtx) libConfig.SetClientCtx(val.ClientCtx)

View File

@ -2,6 +2,7 @@ package moduleobject
import ( import (
"encoding/hex" "encoding/hex"
"strconv"
"github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
@ -51,7 +52,40 @@ func Machine(name, pubKey string, prvKey string, address string) machinetypes.Ma
m := machinetypes.Machine{ m := machinetypes.Machine{
Name: name, Name: name,
Ticker: name + "_ticker", 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, Reissue: true,
Amount: 1000, Amount: 1000,
Precision: 8, Precision: 8,

View File

@ -334,6 +334,9 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) {
ctx := server.NewDefaultContext() ctx := server.NewDefaultContext()
tmCfg := ctx.Config tmCfg := ctx.Config
tmCfg.Consensus.TimeoutCommit = cfg.TimeoutCommit 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 // Only allow the first validator to expose an RPC, API and gRPC
// server/client due to Tendermint in-process constraints. // server/client due to Tendermint in-process constraints.

View File

@ -1,17 +1,29 @@
package util package util
import ( import (
"crypto/sha256"
"encoding/json"
"fmt"
"strings" "strings"
"sync"
"github.com/planetmint/planetmint-go/config" "github.com/planetmint/planetmint-go/config"
"github.com/planetmint/planetmint-go/x/machine/types"
elements "github.com/rddl-network/elements-rpc" 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) { func ReissueAsset(reissueTx string) (txID string, err error) {
conf := config.GetConfig() conf := config.GetConfig()
url := conf.GetRPCURL() url := conf.GetRPCURL()
cmdArgs := strings.Split(reissueTx, " ") cmdArgs := strings.Split(reissueTx, " ")
elementsSyncAccess.Lock()
defer elementsSyncAccess.Unlock()
result, err := elements.ReissueAsset(url, []string{cmdArgs[1], cmdArgs[2]}) result, err := elements.ReissueAsset(url, []string{cmdArgs[1], cmdArgs[2]})
if err != nil { if err != nil {
return return
@ -24,6 +36,8 @@ func DistributeAsset(address string, amount string, reissuanceAsset string) (txI
conf := config.GetConfig() conf := config.GetConfig()
url := conf.GetRPCURL() url := conf.GetRPCURL()
elementsSyncAccess.Lock()
defer elementsSyncAccess.Unlock()
txID, err = elements.SendToAddress(url, []string{ txID, err = elements.SendToAddress(url, []string{
address, address,
`"` + amount + `"`, `"` + amount + `"`,
@ -38,3 +52,97 @@ func DistributeAsset(address string, amount string, reissuanceAsset string) (txI
}) })
return 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
}

View File

@ -3,20 +3,16 @@ package util
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/sha256"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
errorsmod "cosmossdk.io/errors" errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
config "github.com/planetmint/planetmint-go/config"
"github.com/planetmint/planetmint-go/x/machine/types" "github.com/planetmint/planetmint-go/x/machine/types"
elements "github.com/rddl-network/elements-rpc"
) )
type HTTPClient interface { type HTTPClient interface {
@ -31,97 +27,6 @@ func init() {
RegisterAssetServiceHTTPClient = &http.Client{} 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 { func IssueMachineNFT(goCtx context.Context, machine *types.Machine, scheme string, domain string, path string) error {
ctx := sdk.UnwrapSDKContext(goCtx) ctx := sdk.UnwrapSDKContext(goCtx)
// asset registration is in order to have the contact published // asset registration is in order to have the contact published

View File

@ -3,6 +3,9 @@ package util_test
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"math/rand"
"strconv"
"sync"
"testing" "testing"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
@ -47,11 +50,21 @@ func TestMachineNFTIssuance(t *testing.T) {
elements.Client = &elementsmocks.MockClient{} elements.Client = &elementsmocks.MockClient{}
util.RegisterAssetServiceHTTPClient = &mocks.MockClient{} util.RegisterAssetServiceHTTPClient = &mocks.MockClient{}
_, ctx := keeper.MachineKeeper(t) _, ctx := keeper.MachineKeeper(t)
sk, pk := sample.KeyPair()
machine := moduleobject.Machine(pk, pk, sk, "")
goCtx := sdk.WrapSDKContext(ctx)
params := types.DefaultParams() params := types.DefaultParams()
err := util.IssueMachineNFT(goCtx, &machine, params.AssetRegistryScheme, params.AssetRegistryDomain, params.AssetRegistryPath) var wg sync.WaitGroup
assert.NoError(t, err)
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()
} }

View File

@ -2,4 +2,6 @@ package util
import "sync" import "sync"
var TerminationWaitGroup sync.WaitGroup var (
TerminationWaitGroup sync.WaitGroup
)