Skip to content

Commit

Permalink
core: move NotaryAssisted attribute out of P2PSigExtensions
Browse files Browse the repository at this point in the history
Move it under Domovoi hardfork.

Signed-off-by: Anna Shaleva <[email protected]>
  • Loading branch information
AnnaShaleva committed Jul 15, 2024
1 parent b735f2e commit 1c3644f
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 53 deletions.
2 changes: 1 addition & 1 deletion docs/node-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ protocol-related settings described in the table below.
| MaxValidUntilBlockIncrement | `uint32` | `5760` | Upper height increment limit for transaction's ValidUntilBlock field value relative to the current blockchain height, exceeding which a transaction will fail validation. It is set to estimated daily number of blocks with 15s interval by default. |
| MemPoolSize | `int` | `50000` | Size of the node's memory pool where transactions are stored before they are added to block. |
| P2PNotaryRequestPayloadPoolSize | `int` | `1000` | Size of the node's P2P Notary request payloads memory pool where P2P Notary requests are stored before main or fallback transaction is completed and added to the chain.<br>This option is valid only if `P2PSigExtensions` are enabled. | Not supported by the C# node, thus may affect heterogeneous networks functionality. |
| P2PSigExtensions | `bool` | `false` | Enables following additional Notary service related logic:<br>• Transaction attribute `NotaryAssisted`<br>• Network payload of the `P2PNotaryRequest` type<br>• Notary node module | Not supported by the C# node, thus may affect heterogeneous networks functionality. |
| P2PSigExtensions | `bool` | `false` | Enables following additional Notary service related logic:<br>• Network payload of the `P2PNotaryRequest` type<br>• Notary node module | Not supported by the C# node, thus may affect heterogeneous networks functionality. |
| P2PStateExchangeExtensions | `bool` | `false` | Enables the following P2P MPT state data exchange logic: <br>• `StateSyncInterval` protocol setting <br>• P2P commands `GetMPTDataCMD` and `MPTDataCMD` | Not supported by the C# node, thus may affect heterogeneous networks functionality. Can be supported either on MPT-complete node (`KeepOnlyLatestState`=`false`) or on light GC-enabled node (`RemoveUntraceableBlocks=true`) in which case `KeepOnlyLatestState` setting doesn't change the behavior, an appropriate set of MPTs is always stored (see `RemoveUntraceableBlocks`). |
| ReservedAttributes | `bool` | `false` | Allows to have reserved attributes range for experimental or private purposes. |
| SeedList | `[]string` | [] | List of initial nodes addresses used to establish connectivity. |
Expand Down
4 changes: 2 additions & 2 deletions pkg/core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -2697,8 +2697,8 @@ func (bc *Blockchain) verifyTxAttributes(d *dao.Simple, tx *transaction.Transact
return fmt.Errorf("%w: conflicting transaction %s is already on chain", ErrInvalidAttribute, conflicts.Hash.StringLE())
}
case transaction.NotaryAssistedT:
if !bc.config.P2PSigExtensions {
return fmt.Errorf("%w: NotaryAssisted attribute was found, but P2PSigExtensions are disabled", ErrInvalidAttribute)
if !bc.isHardforkEnabled(&transaction.NotaryAssistedActivation, bc.BlockHeight()) {
return fmt.Errorf("%w: NotaryAssisted attribute was found, but %s is not active yet", ErrInvalidAttribute, transaction.NotaryAssistedActivation)
}
if !tx.HasSigner(nativehashes.Notary) {
return fmt.Errorf("%w: NotaryAssisted attribute was found, but transaction is not signed by the Notary native contract", ErrInvalidAttribute)
Expand Down
9 changes: 7 additions & 2 deletions pkg/core/blockchain_neotest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2057,7 +2057,12 @@ func TestBlockchain_VerifyTx(t *testing.T) {
}
t.Run("Disabled", func(t *testing.T) {
bcBad, validatorBad, committeeBad := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) {
c.P2PSigExtensions = false
c.P2PSigExtensions = true
c.Hardforks = map[string]uint32{
config.HFAspidochelone.String(): 0,
config.HFBasilisk.String(): 0,
config.HFCockatrice.String(): 0,
}
c.ReservedAttributes = false
})
eBad := neotest.NewExecutor(t, bcBad, validatorBad, committeeBad)
Expand All @@ -2069,7 +2074,7 @@ func TestBlockchain_VerifyTx(t *testing.T) {
eBad.SignTx(t, tx, 1_0000_0000, eBad.Committee)
err := bcBad.VerifyTx(tx)
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "invalid attribute: NotaryAssisted attribute was found, but P2PSigExtensions are disabled"))
require.True(t, strings.Contains(err.Error(), "invalid attribute: NotaryAssisted attribute was found, but Echidna is not active yet"))
})
t.Run("Enabled, insufficient network fee", func(t *testing.T) {
tx := getNotaryAssistedTx(e, 1, 0)
Expand Down
4 changes: 2 additions & 2 deletions pkg/core/native/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ func NewContracts(cfg config.ProtocolConfiguration) *Contracts {
cs.Ledger = ledger
cs.Contracts = append(cs.Contracts, ledger)

gas := newGAS(int64(cfg.InitialGASSupply), cfg.P2PSigExtensions)
gas := newGAS(int64(cfg.InitialGASSupply))
neo := newNEO(cfg)
policy := newPolicy(cfg.P2PSigExtensions)
policy := newPolicy()
neo.GAS = gas
neo.Policy = policy
gas.NEO = neo
Expand Down
4 changes: 2 additions & 2 deletions pkg/core/native/management_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (

func TestDeployGetUpdateDestroyContract(t *testing.T) {
mgmt := newManagement()
mgmt.Policy = newPolicy(false)
mgmt.Policy = newPolicy()
d := dao.NewSimple(storage.NewMemoryStore(), false)
ic := &interop.Context{DAO: d}
err := mgmt.Initialize(ic, nil, nil)
Expand Down Expand Up @@ -95,7 +95,7 @@ func TestManagement_Initialize(t *testing.T) {

func TestManagement_GetNEP17Contracts(t *testing.T) {
mgmt := newManagement()
mgmt.Policy = newPolicy(false)
mgmt.Policy = newPolicy()
d := dao.NewSimple(storage.NewMemoryStore(), false)
err := mgmt.Initialize(&interop.Context{DAO: d}, nil, nil)
require.NoError(t, err)
Expand Down
22 changes: 9 additions & 13 deletions pkg/core/native/native_gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ type GAS struct {
NEO *NEO
Policy *Policy

initialSupply int64
p2pSigExtensionsEnabled bool
initialSupply int64
}

const gasContractID = -6
Expand All @@ -32,10 +31,9 @@ const gasContractID = -6
const GASFactor = NEOTotalSupply

// newGAS returns GAS native contract.
func newGAS(init int64, p2pSigExtensionsEnabled bool) *GAS {
func newGAS(init int64) *GAS {
g := &GAS{
initialSupply: init,
p2pSigExtensionsEnabled: p2pSigExtensionsEnabled,
initialSupply: init,
}
defer g.BuildHFSpecificMD(g.ActiveIn())

Expand Down Expand Up @@ -122,14 +120,12 @@ func (g *GAS) OnPersist(ic *interop.Context) error {
var netFee int64
for _, tx := range ic.Block.Transactions {
netFee += tx.NetworkFee
if g.p2pSigExtensionsEnabled {
// Reward for NotaryAssisted attribute will be minted to designated notary nodes
// by Notary contract.
attrs := tx.GetAttributes(transaction.NotaryAssistedT)
if len(attrs) != 0 {
na := attrs[0].Value.(*transaction.NotaryAssisted)
netFee -= (int64(na.NKeys) + 1) * g.Policy.GetAttributeFeeInternal(ic.DAO, transaction.NotaryAssistedT)
}
// Reward for NotaryAssisted attribute will be minted to designated notary nodes
// by Notary contract.
attrs := tx.GetAttributes(transaction.NotaryAssistedT)
if len(attrs) != 0 {
na := attrs[0].Value.(*transaction.NotaryAssisted)
netFee -= (int64(na.NKeys) + 1) * g.Policy.GetAttributeFeeInternal(ic.DAO, transaction.NotaryAssistedT)
}
}
g.mint(ic, primary, big.NewInt(int64(netFee)), false)
Expand Down
8 changes: 8 additions & 0 deletions pkg/core/native/native_test/management_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ var (
// under assumption that hardforks from Aspidochelone to Echidna (included) are enabled.
echidnaCSS = map[string]string{
nativenames.Notary: `{"id":-10,"hash":"0xc1e14f19c3e60d0b9244d06dd7ba9b113135ec3b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1110259869},"manifest":{"name":"Notary","abi":{"methods":[{"name":"balanceOf","offset":0,"parameters":[{"name":"addr","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"expirationOf","offset":7,"parameters":[{"name":"addr","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"getMaxNotValidBeforeDelta","offset":14,"parameters":[],"returntype":"Integer","safe":true},{"name":"lockDepositUntil","offset":21,"parameters":[{"name":"address","type":"Hash160"},{"name":"till","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"onNEP17Payment","offset":28,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"setMaxNotValidBeforeDelta","offset":35,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"verify","offset":42,"parameters":[{"name":"signature","type":"Signature"}],"returntype":"Boolean","safe":true},{"name":"withdraw","offset":49,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"}],"returntype":"Boolean","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`,
nativenames.Policy: `{"id":-7,"hash":"0xcc5e4edd9f5f8dba8bb65734541df7a1c081c67b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"PolicyContract","abi":{"methods":[{"name":"blockAccount","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","safe":false},{"name":"getAttributeFee","offset":7,"parameters":[{"name":"attributeType","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"getExecFeeFactor","offset":14,"parameters":[],"returntype":"Integer","safe":true},{"name":"getFeePerByte","offset":21,"parameters":[],"returntype":"Integer","safe":true},{"name":"getStoragePrice","offset":28,"parameters":[],"returntype":"Integer","safe":true},{"name":"isBlocked","offset":35,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","safe":true},{"name":"setAttributeFee","offset":42,"parameters":[{"name":"attributeType","type":"Integer"},{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setExecFeeFactor","offset":49,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setFeePerByte","offset":56,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setStoragePrice","offset":63,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"unblockAccount","offset":70,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`,
}
)

Expand Down Expand Up @@ -255,6 +256,13 @@ func TestManagement_NativeDeployUpdateNotifications(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 1, len(aer))
expected = expected[:0]
expected = append(expected, state.NotificationEvent{
ScriptHash: nativehashes.ContractManagement,
Name: "Update",
Item: stackitem.NewArray([]stackitem.Item{
stackitem.Make(nativehashes.PolicyContract),
}),
})
expected = append(expected, state.NotificationEvent{
ScriptHash: nativehashes.ContractManagement,
Name: "Deploy",
Expand Down
85 changes: 54 additions & 31 deletions pkg/core/native/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,6 @@ var (
type Policy struct {
interop.ContractMD
NEO *NEO

// p2pSigExtensionsEnabled defines whether the P2P signature extensions logic is relevant.
p2pSigExtensionsEnabled bool
}

type PolicyCache struct {
Expand Down Expand Up @@ -100,11 +97,8 @@ func copyPolicyCache(src, dst *PolicyCache) {
}

// newPolicy returns Policy native contract.
func newPolicy(p2pSigExtensionsEnabled bool) *Policy {
p := &Policy{
ContractMD: *interop.NewContractMD(nativenames.Policy, policyContractID),
p2pSigExtensionsEnabled: p2pSigExtensionsEnabled,
}
func newPolicy() *Policy {
p := &Policy{ContractMD: *interop.NewContractMD(nativenames.Policy, policyContractID)}
defer p.BuildHFSpecificMD(p.ActiveIn())

desc := newDescriptor("getFeePerByte", smartcontract.IntegerType)
Expand Down Expand Up @@ -136,13 +130,24 @@ func newPolicy(p2pSigExtensionsEnabled bool) *Policy {

desc = newDescriptor("getAttributeFee", smartcontract.IntegerType,
manifest.NewParameter("attributeType", smartcontract.IntegerType))
md = newMethodAndPrice(p.getAttributeFee, 1<<15, callflag.ReadStates)
md = newMethodAndPrice(p.getAttributeFeeV0, 1<<15, callflag.ReadStates, config.HFDefault, transaction.NotaryAssistedActivation)
p.AddMethod(md, desc)

desc = newDescriptor("getAttributeFee", smartcontract.IntegerType,
manifest.NewParameter("attributeType", smartcontract.IntegerType))
md = newMethodAndPrice(p.getAttributeFeeV1, 1<<15, callflag.ReadStates, transaction.NotaryAssistedActivation)
p.AddMethod(md, desc)

desc = newDescriptor("setAttributeFee", smartcontract.VoidType,
manifest.NewParameter("attributeType", smartcontract.IntegerType),
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(p.setAttributeFee, 1<<15, callflag.States)
md = newMethodAndPrice(p.setAttributeFeeV0, 1<<15, callflag.States, config.HFDefault, transaction.NotaryAssistedActivation)
p.AddMethod(md, desc)

desc = newDescriptor("setAttributeFee", smartcontract.VoidType,
manifest.NewParameter("attributeType", smartcontract.IntegerType),
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(p.setAttributeFeeV1, 1<<15, callflag.States, transaction.NotaryAssistedActivation)
p.AddMethod(md, desc)

desc = newDescriptor("setFeePerByte", smartcontract.VoidType,
Expand Down Expand Up @@ -170,27 +175,27 @@ func (p *Policy) Metadata() *interop.ContractMD {

// Initialize initializes Policy native contract and implements the Contract interface.
func (p *Policy) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
if hf != p.ActiveIn() {
return nil
}

setIntWithKey(p.ID, ic.DAO, feePerByteKey, defaultFeePerByte)
setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, defaultExecFeeFactor)
setIntWithKey(p.ID, ic.DAO, storagePriceKey, DefaultStoragePrice)

cache := &PolicyCache{
execFeeFactor: defaultExecFeeFactor,
feePerByte: defaultFeePerByte,
maxVerificationGas: defaultMaxVerificationGas,
storagePrice: DefaultStoragePrice,
attributeFee: map[transaction.AttrType]uint32{},
blockedAccounts: make([]util.Uint160, 0),
if hf == p.ActiveIn() {
setIntWithKey(p.ID, ic.DAO, feePerByteKey, defaultFeePerByte)
setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, defaultExecFeeFactor)
setIntWithKey(p.ID, ic.DAO, storagePriceKey, DefaultStoragePrice)

cache := &PolicyCache{
execFeeFactor: defaultExecFeeFactor,
feePerByte: defaultFeePerByte,
maxVerificationGas: defaultMaxVerificationGas,
storagePrice: DefaultStoragePrice,
attributeFee: map[transaction.AttrType]uint32{},
blockedAccounts: make([]util.Uint160, 0),
}
ic.DAO.SetCache(p.ID, cache)
}
if p.p2pSigExtensionsEnabled {
if hf != nil && *hf == transaction.NotaryAssistedActivation {
setIntWithKey(p.ID, ic.DAO, []byte{attributeFeePrefix, byte(transaction.NotaryAssistedT)}, defaultNotaryAssistedFee)

cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
cache.attributeFee[transaction.NotaryAssistedT] = defaultNotaryAssistedFee
}
ic.DAO.SetCache(p.ID, cache)

return nil
}
Expand Down Expand Up @@ -363,9 +368,18 @@ func (p *Policy) setStoragePrice(ic *interop.Context, args []stackitem.Item) sta
return stackitem.Null{}
}

func (p *Policy) getAttributeFee(ic *interop.Context, args []stackitem.Item) stackitem.Item {
func (p *Policy) getAttributeFeeV0(ic *interop.Context, args []stackitem.Item) stackitem.Item {
return p.getAttributeFeeGeneric(ic, args, false)
}

func (p *Policy) getAttributeFeeV1(ic *interop.Context, args []stackitem.Item) stackitem.Item {
return p.getAttributeFeeGeneric(ic, args, true)
}

func (p *Policy) getAttributeFeeGeneric(ic *interop.Context, args []stackitem.Item, allowNotaryAssisted bool) stackitem.Item {
t := transaction.AttrType(toUint8(args[0]))
if !transaction.IsValidAttrType(ic.Chain.GetConfig().ReservedAttributes, t) {
if !transaction.IsValidAttrType(ic.Chain.GetConfig().ReservedAttributes, t) ||
(!allowNotaryAssisted && t == transaction.NotaryAssistedT) {
panic(fmt.Errorf("invalid attribute type: %d", t))
}
return stackitem.NewBigInteger(big.NewInt(p.GetAttributeFeeInternal(ic.DAO, t)))
Expand All @@ -382,10 +396,19 @@ func (p *Policy) GetAttributeFeeInternal(d *dao.Simple, t transaction.AttrType)
return int64(v)
}

func (p *Policy) setAttributeFee(ic *interop.Context, args []stackitem.Item) stackitem.Item {
func (p *Policy) setAttributeFeeV0(ic *interop.Context, args []stackitem.Item) stackitem.Item {
return p.setAttributeFeeGeneric(ic, args, false)
}

func (p *Policy) setAttributeFeeV1(ic *interop.Context, args []stackitem.Item) stackitem.Item {
return p.setAttributeFeeGeneric(ic, args, true)
}

func (p *Policy) setAttributeFeeGeneric(ic *interop.Context, args []stackitem.Item, allowNotaryAssisted bool) stackitem.Item {
t := transaction.AttrType(toUint8(args[0]))
value := toUint32(args[1])
if !transaction.IsValidAttrType(ic.Chain.GetConfig().ReservedAttributes, t) {
if !transaction.IsValidAttrType(ic.Chain.GetConfig().ReservedAttributes, t) ||
(!allowNotaryAssisted && t == transaction.NotaryAssistedT) {
panic(fmt.Errorf("invalid attribute type: %d", t))
}
if value > maxAttributeFee {
Expand Down
51 changes: 51 additions & 0 deletions pkg/core/native/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import (
"fmt"
"testing"

"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativehashes"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -65,3 +68,51 @@ func TestPolicy_BlockedAccounts(t *testing.T) {
require.Equal(t, expectedErr, err.Error())
})
}

func TestPolicy_GetNotaryFeePerKey(t *testing.T) {
const echidnaHeight = 4
bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.Blockchain) {
c.Hardforks = map[string]uint32{
config.HFEchidna.String(): echidnaHeight,
}
})
e := neotest.NewExecutor(t, bc, acc, acc)
p := e.CommitteeInvoker(nativehashes.PolicyContract)

// Invoke before Echidna should fail.
p.InvokeFail(t, "invalid attribute type: 34", "getAttributeFee", int64(transaction.NotaryAssistedT))

for e.Chain.BlockHeight() < echidnaHeight-1 {
e.AddNewBlock(t)
}

// Invoke at Echidna should return the default value.
p.Invoke(t, 1000_0000, "getAttributeFee", int64(transaction.NotaryAssistedT))

// Invoke after Echidna should return the default value.
p.Invoke(t, 1000_0000, "getAttributeFee", int64(transaction.NotaryAssistedT))
}

func TestPolicy_SetNotaryFeePerKey(t *testing.T) {
const echidnaHeight = 4
bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.Blockchain) {
c.Hardforks = map[string]uint32{
config.HFEchidna.String(): echidnaHeight,
}
})
e := neotest.NewExecutor(t, bc, acc, acc)
p := e.CommitteeInvoker(nativehashes.PolicyContract)

// Invoke before Echidna should fail.
p.InvokeFail(t, "invalid attribute type: 34", "setAttributeFee", int64(transaction.NotaryAssistedT), 500_0000)

for e.Chain.BlockHeight() < echidnaHeight-1 {
e.AddNewBlock(t)
}

// Invoke at Echidna should return the default value.
p.Invoke(t, nil, "setAttributeFee", int64(transaction.NotaryAssistedT), 500_0000)

// Invoke after Echidna should return the default value.
p.Invoke(t, nil, "setAttributeFee", int64(transaction.NotaryAssistedT), 510_0000)
}
5 changes: 5 additions & 0 deletions pkg/core/transaction/notary_assisted.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package transaction

import (
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/io"
)

// NotaryAssistedActivation stores the hardfork of NotaryAssisted transaction attribute
// activation.
var NotaryAssistedActivation = config.HFEchidna

// NotaryAssisted represents attribute for notary service transactions.
type NotaryAssisted struct {
NKeys uint8 `json:"nkeys"`
Expand Down

0 comments on commit 1c3644f

Please sign in to comment.